WebOb Documentation Release 1.6.4
Ian Bicking and contributors
Mar 16, 2017
Contents
1 WebOb Reference 3 1.1 Introduction...... 4 1.2 Request...... 4 1.3 Response...... 11 1.4 Exceptions...... 17
2 Differences Between WebOb and Other Systems 19 2.1 paste.wsgiwrappers and Pylons...... 20 2.2 Django...... 21 2.3 CherryPy/TurboGears...... 22 2.4 Yaro...... 23 2.5 Werkzeug...... 24 2.6 Zope 3...... 24 2.7 mod_python...... 26 2.8 webapp Response...... 26 2.9 PHP...... 27
3 License 29
4 API Documentation 31 4.1 webob.client – Send WSGI requests over HTTP...... 31 4.2 webob.cookies – Cookies...... 32 4.3 webob.dec – WSGIfy decorator...... 34 4.4 webob.exc – WebOb Exceptions...... 36 4.5 webob.multidict – multi-value dictionary object...... 46 4.6 webob.request – Request...... 47 4.7 webob.response – Response...... 52 4.8 webob.static – Serving static files...... 56 4.9 webob – Request/Response objects...... 56
5 Request 61 5.1 URLs...... 61 5.2 Methods...... 62 5.3 Unicode...... 62
6 Response 63 6.1 Headers...... 64
i 6.2 Instantiating the Response...... 64
7 Exceptions 65
8 Multidict 67
9 Example 69 9.1 WebOb File-Serving Example...... 69 9.2 Wiki Example...... 72 9.3 Comment Example...... 83 9.4 JSON-RPC Example...... 89 9.5 Another Do-It-Yourself Framework...... 99
10 Change History 111 10.1 What’s New in WebOb 1.5...... 111 10.2 What’s New in WebOb 1.6...... 112 10.3 WebOb Change History...... 113
11 Status & License 133
Python Module Index 135
ii WebOb Documentation, Release 1.6.4
WebOb provides objects for HTTP requests and responses. Specifically it does this by wrapping the WSGI request environment and response status/headers/app_iter(body). The request and response objects provide many conveniences for parsing HTTP request and forming HTTP responses. Both objects are read/write: as a result, WebOb is also a nice way to create HTTP requests and parse HTTP responses; however, we won’t cover that use case in this document. The reference documentation shows many examples of creating requests.
Contents 1 WebOb Documentation, Release 1.6.4
2 Contents CHAPTER 1
WebOb Reference
Contents
• WebOb Reference – Introduction – Request
* Request Body * Method & URL * Headers * Query & POST variables · Unicode Variables
* Cookies * Modifying the request * Header Getters · Accept-* headers · Conditional Requests
* Calling WSGI Applications * Ad-Hoc Attributes – Response
* Core Attributes * Headers * Body & app_iter
3 WebOb Documentation, Release 1.6.4
* Header Getters * Cookies * Binding a Request * Response as a WSGI application – Exceptions
* Conditional WSGI Application
Introduction
This document covers all the details of the Request and Response objects. It is written to be testable with doctest– this affects the flavor of the documentation, perhaps to its detriment. But it also means you can feel confident that the documentation is correct. This is a somewhat different approach to reference documentation compared to the extracted documentation for the request and response.
Request
The primary object in WebOb is webob.Request, a wrapper around a WSGI environment. The basic way you create a request object is simple enough:
>>> from webob import Request >>> environ={'wsgi.url_scheme':'http',...} >>> req= Request(environ)
(Note that the WSGI environment is a dictionary with a dozen required keys, so it’s a bit lengthly to show a complete example of what it would look like – usually your WSGI server will create it.) The request object wraps the environment; it has very little internal state of its own. Instead attributes you access read and write to the environment dictionary. You don’t have to understand the details of WSGI to use this library; this library handles those details for you. You also don’t have to use this exclusively of other libraries. If those other libraries also keep their state in the environment, multiple wrappers can coexist. Examples of libraries that can coexist include paste.wsgiwrappers.Request (used by Pylons) and yaro.Request. The WSGI environment has a number of required variables. To make it easier to test and play around with, the Request class has a constructor that will fill in a minimal environment:
>>> req= Request.blank('/article?id=1') >>> from pprint import pprint >>> pprint(req.environ) {'HTTP_HOST': 'localhost:80', 'PATH_INFO': '/article', 'QUERY_STRING': 'id=1', 'REQUEST_METHOD': 'GET', 'SCRIPT_NAME': '', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '80', 'SERVER_PROTOCOL': 'HTTP/1.0',
4 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
'wsgi.errors':
Request Body req.body is a file-like object that gives the body of the request (e.g., a POST form, the body of a PUT, etc). It’s kind of boring to start, but you can set it to a string and that will be turned into a file-like object. You can read the entire body with req.body.
>>> hasattr(req.body_file,'read') True >>> req.body '' >>> req.method='PUT' >>> req.body='test' >>> hasattr(req.body_file,'read') True >>> req.body 'test'
Method & URL
All the normal parts of a request are also accessible through the request object:
>>> req.method 'PUT' >>> req.scheme 'http' >>> req.script_name # The base of the URL '' >>> req.script_name='/blog' # make it more interesting >>> req.path_info # The yet-to-be-consumed part of the URL '/article' >>> req.content_type # Content-Type of the request body '' >>> print req.remote_user # The authenticated user (there is none set) None >>> print req.remote_addr # The remote IP None >>> req.host 'localhost:80' >>> req.host_url 'http://localhost' >>> req.application_url 'http://localhost/blog' >>> req.path_url 'http://localhost/blog/article' >>> req.url 'http://localhost/blog/article?id=1'
1.2. Request 5 WebOb Documentation, Release 1.6.4
>>> req.path '/blog/article' >>> req.path_qs '/blog/article?id=1' >>> req.query_string 'id=1'
You can make new URLs:
>>> req.relative_url('archive') 'http://localhost/blog/archive'
For parsing the URLs, it is often useful to deal with just the next path segment on PATH_INFO:
>>> req.path_info_peek() # Doesn't change request 'article' >>> req.path_info_pop() # Does change request! 'article' >>> req.script_name '/blog/article' >>> req.path_info ''
Headers
All request headers are available through a dictionary-like object req.headers. Keys are case-insensitive.
>>> req.headers['Content-Type']='application/x-www-urlencoded' >>> sorted(req.headers.items()) [('Content-Length', '4'), ('Content-Type', 'application/x-www-urlencoded'), ('Host',
˓→'localhost:80')] >>> req.environ['CONTENT_TYPE'] 'application/x-www-urlencoded'
Query & POST variables
Requests can have variables in one of two locations: the query string (?id=1), or in the body of the request (generally a POST form). Note that even POST requests can have a query string, so both kinds of variables can exist at the same time. Also, a variable can show up more than once, as in ?check=a&check=b. For these variables WebOb uses a MultiDict, which is basically a dictionary wrapper on a list of key/value pairs. It looks like a single-valued dictionary, but you can access all the values of a key with .getall(key) (which always returns a list, possibly an empty list). You also get all key/value pairs when using .items() and all values with .values(). Some examples:
>>> req= Request.blank('/test?check=a&check=b&name=Bob') >>> req.GET MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]) >>> req.GET['check'] u'b' >>> req.GET.getall('check') [u'a', u'b']
6 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
>>> req.GET.items() [(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]
We’ll have to create a request body and change the method to get POST. Until we do that, the variables are boring:
>>> req.POST
Often you won’t care where the variables come from. (Even if you care about the method, the location of the variables might not be important.) There is a dictionary called req.params that contains variables from both sources:
>>> req.params NestedMultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob'), (u'name', u
˓→'Joe'), (u'email', u'[email protected]')]) >>> req.params['name'] u'Bob' >>> req.params.getall('name') [u'Bob', u'Joe'] >>> for name, value in req.params.items(): ... print '%s: %r'% (name, value) check: u'a' check: u'b' name: u'Bob' name: u'Joe' email: u'[email protected]'
The POST and GET nomenclature is historical – req.GET can be used for non-GET requests to access query param- eters, and req.POST can also be used for PUT requests with the appropriate Content-Type.
>>> req= Request.blank('/test?check=a&check=b&name=Bob') >>> req.method='PUT' >>> req.body= body='var1=value1&var2=value2&rep=1&rep=2' >>> req.environ['CONTENT_LENGTH']= str(len(req.body)) >>> req.environ['CONTENT_TYPE']='application/x-www-form-urlencoded' >>> req.GET MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')]) >>> req.POST MultiDict([(u'var1', u'value1'), (u'var2', u'value2'), (u'rep', u'1'), (u'rep', u'2
˓→')])
Unicode Variables
Submissions are non-unicode (str) strings, unless some character set is indicated. A client can indicate the char- acter set with Content-Type: application/x-www-form-urlencoded; charset=utf8, but very few clients actually do this (sometimes XMLHttpRequest requests will do this, as JSON is always UTF8 even when a page is served with a different character set). You can force a charset, which will affect all the variables:
1.2. Request 7 WebOb Documentation, Release 1.6.4
>>> req.charset='utf8' >>> req.GET MultiDict([(u'check', u'a'), (u'check', u'b'), (u'name', u'Bob')])
Cookies
Cookies are presented in a simple dictionary. Like other variables, they will be decoded into Unicode strings if you set the charset.
>>> req.headers['Cookie']='test=value' >>> req.cookies MultiDict([(u'test', u'value')])
Modifying the request
The headers are all modifiable, as are other environmental variables (like req.remote_user, which maps to request.environ['REMOTE_USER']). If you want to copy the request you can use req.copy(); this copies the environ dictionary, and the request body from environ['wsgi.input']. The method req.remove_conditional_headers(remove_encoding=True) can be used to remove headers that might result in a 304 Not Modified response. If you are writing some intermediary it can be useful to avoid these headers. Also if remove_encoding is true (the default) then any Accept-Encoding header will be removed, which can result in gzipped responses.
Header Getters
In addition to req.headers, there are attributes for most of the request headers defined by the HTTP 1.1 specifica- tion. These attributes often return parsed forms of the headers.
Accept-* headers
There are several request headers that tell the server what the client accepts. These are accept (the Content-Type that is accepted), accept_charset (the charset accepted), accept_encoding (the Content-Encoding, like gzip, that is accepted), and accept_language (generally the preferred language of the client). The objects returned support containment to test for acceptability. E.g.:
>>> 'text/html' in req.accept True
Because no header means anything is potentially acceptable, this is returning True. We can set it to see more interesting behavior (the example means that text/html is okay, but application/xhtml+xml is preferred):
>>> req.accept='text/html;q=0.5, application/xhtml+xml;q=1' >>> req.accept
There are a few methods for different strategies of finding a match.
8 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
>>> req.accept.best_match(['text/html','application/xhtml+xml']) 'application/xhtml+xml'
If we just want to know everything the client prefers, in the order it is preferred:
>>> list(req.accept) ['application/xhtml+xml', 'text/html']
For languages you’ll often have a “fallback” language. E.g., if there’s nothing better then use en-US (and if en-US is okay, ignore any less preferrable languages):
>>> req.accept_language='es, pt-BR' >>> req.accept_language.best_match(['en-GB','en-US'], default_match='en-US') 'en-US' >>> req.accept_language.best_match(['es','en-US'], default_match='en-US') 'es'
Your fallback language must appear both in the offers and as the default_match to insure that it is returned as a best match if the client specified a preference for it.
>>> req.accept_language='en-US;q=0.5, en-GB;q=0.2' >>> req.accept_language.best_match(['en-GB'], default_match='en-US') 'en-GB' >>> req.accept_language.best_match(['en-GB','en-US'], default_match='en-US') 'en-US'
Conditional Requests
There a number of ways to make a conditional request. A conditional request is made when the client has a document, but it is not sure if the document is up to date. If it is not, it wants a new version. If the document is up to date then it doesn’t want to waste the bandwidth, and expects a 304 Not Modified response. ETags are generally the best technique for these kinds of requests; this is an opaque string that indicates the identity of the object. For instance, it’s common to use the mtime (last modified) of the file, plus the number of bytes, and maybe a hash of the filename (if there’s a possibility that the same URL could point to two different server-side filenames based on other variables). To test if a 304 response is appropriate, you can use:
>>> server_token='opaque-token' >>> server_token in req.if_none_match # You shouldn't return 304 False >>> req.if_none_match= server_token >>> req.if_none_match
For date-based comparisons If-Modified-Since is used:
>>> from webob import UTC >>> from datetime import datetime >>> req.if_modified_since= datetime(2006,1,1, 12,0, tzinfo=UTC) >>> req.headers['If-Modified-Since'] 'Sun, 01 Jan 2006 12:00:00 GMT' >>> server_modified= datetime(2005,1,1, 12,0, tzinfo=UTC) >>> req.if_modified_since and req.if_modified_since>= server_modified True
1.2. Request 9 WebOb Documentation, Release 1.6.4
For range requests there are two important headers, If-Range (which is form of conditional request) and Range (which requests a range). If the If-Range header fails to match then the full response (not a range) should be returned:
>>> req.if_range
You can also pass in a response object with:
>>> from webob import Response >>> res= Response(etag='opaque-etag') >>> req.if_range.match_response(res) True
To get the range information:
>>> req.range='bytes=0-100' >>> req.range
Note that the range headers use inclusive ranges (the last byte indexed is included), where Python always uses a range where the last index is excluded from the range. The .stop index is in the Python form. Another kind of conditional request is a request (typically PUT) that includes If-Match or If-Unmodified-Since. In this case you are saying “here is an update to a resource, but don’t apply it if someone else has done something since I last got the resource”. If-Match means “do this if the current ETag matches the ETag I’m giving”. If-Unmodified-Since means “do this if the resource has remained unchanged”.
>>> server_token in req.if_match # No If-Match means everything is ok True >>> req.if_match= server_token >>> server_token in req.if_match # Still OK True >>> req.if_match='other-token' >>> # Not OK, should return 412 Precondition Failed: >>> server_token in req.if_match False
For more on this kind of conditional request, see Detecting the Lost Update Problem Using Unreserved Checkout.
Calling WSGI Applications
The request object can be used to make handy subrequests or test requests against WSGI applications. If you want to make subrequests, you should copy the request (with req.copy()) before sending it to multiple applications, since applications might modify the request when they are run. There’s two forms of the subrequest. The more primitive form is this:
10 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
>>> req= Request.blank('/') >>> def wsgi_app(environ, start_response): ... start_response('200 OK', [('Content-type','text/plain')]) ... return ['Hi!'] >>> req.call_application(wsgi_app) ('200 OK', [('Content-type', 'text/plain')], ['Hi!'])
Note it returns (status_string, header_list, app_iter). If app_iter.close() exists, it is your responsibility to call it. A handier response can be had with:
>>> res= req.get_response(wsgi_app) >>> res
You can learn more about this response object in the Response section.
Ad-Hoc Attributes
You can assign attributes to your request objects. They will all go in environ['webob.adhoc_attrs'] (a dictionary).
>>> req= Request.blank('/') >>> req.some_attr='blah blah blah' >>> new_req= Request(req.environ) >>> new_req.some_attr 'blah blah blah' >>> req.environ['webob.adhoc_attrs'] {'some_attr': 'blah blah blah'}
Response
The webob.Response object contains everything necessary to make a WSGI response. Instances of it are in fact WSGI applications, but it can also represent the result of calling a WSGI application (as noted in Calling WSGI Applications). It can also be a way of accumulating a response in your WSGI application. A WSGI response is made up of a status (like 200 OK), a list of headers, and a body (or iterator that will produce a body).
Core Attributes
The core attributes are unsurprising:
>>> from webob import Response >>> res= Response() >>> res.status
1.3. Response 11 WebOb Documentation, Release 1.6.4
'200 OK' >>> res.headerlist [('Content-Type', 'text/html; charset=UTF-8'), ('Content-Length', '0')] >>> res.body ''
You can set any of these attributes, e.g.:
>>> res.status= 404 >>> res.status '404 Not Found' >>> res.status_code 404 >>> res.headerlist=[('Content-type','text/html')] >>> res.body='test' >>> print res 404 Not Found Content-type: text/html Content-Length: 4
test >>> res.body= u"test" Traceback (most recent call last): ... TypeError: You cannot set Response.body to a unicode object (use Response.text) >>> res.text= u"test" Traceback (most recent call last): ... AttributeError: You cannot access Response.text unless charset is set >>> res.charset='utf8' >>> res.text= u"test" >>> res.body 'test'
You can set any attribute with the constructor, like Response(charset='utf8')
Headers
In addition to res.headerlist, there is dictionary-like view on the list in res.headers:
>>> res.headers ResponseHeaders([('Content-Type', 'text/html; charset=utf8'), ('Content-Length', '4
˓→')])
This is case-insensitive. It can support multiple values for a key, though only if you use res.headers.add(key, value) or read them with res.headers.getall(key).
Body & app_iter
The res.body attribute represents the entire body of the request as a single string (not unicode, though you can set it to unicode if you have a charset defined). There is also a res.app_iter attribute that reprsents the body as an iterator. WSGI applications return these app_iter iterators instead of strings, and sometimes it can be problematic to load the entire iterator at once (for instance, if it returns the contents of a very large file). Generally it is not a problem, and often the iterator is something simple like a one-item list containing a string with the entire body.
12 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
If you set the body then Content-Length will also be set, and an res.app_iter will be created for you. If you set res.app_iter then Content-Length will be cleared, but it won’t be set for you. There is also a file-like object you can access, which will update the app_iter in-place (turning the app_iter into a list if necessary):
>>> res= Response(content_type='text/plain', charset=None) >>> f= res.body_file >>> f.write('hey') >>> f.write(u'test') Traceback (most recent call last): ... TypeError: You can only write unicode to Response if charset has been set >>> f.encoding >>> res.charset='utf8' >>> f.encoding 'utf8' >>> f.write(u'test') >>> res.app_iter ['', 'hey', 'test'] >>> res.body 'heytest'
Header Getters
Like Request, HTTP response headers are also available as individual properties. These represent parsed forms of the headers. Content-Type is a special case, as the type and the charset are handled through two separate properties:
>>> res= Response() >>> res.content_type='text/html' >>> res.charset='utf8' >>> res.content_type 'text/html' >>> res.headers['content-type'] 'text/html; charset=utf8' >>> res.content_type='application/atom+xml' >>> res.content_type_params {'charset': 'utf8'} >>> res.content_type_params={'type':'entry','charset':'utf8'} >>> res.headers['content-type'] 'application/atom+xml; charset=utf8; type=entry'
Other headers:
>>> # Used with a redirect: >>> res.location='http://localhost/foo'
>>> # Indicates that the server accepts Range requests: >>> res.accept_ranges='bytes'
>>> # Used by caching proxies to tell the client how old the >>> # response is: >>> res.age= 120
>>> # Show what methods the client can do; typically used in
1.3. Response 13 WebOb Documentation, Release 1.6.4
>>> # a 405 Method Not Allowed response: >>> res.allow=['GET','PUT']
>>> # Set the cache-control header: >>> res.cache_control.max_age= 360 >>> res.cache_control.no_transform= True
>>> # Tell the browser to treat the response as an attachment: >>> res.content_disposition='attachment; filename=foo.xml'
>>> # Used if you had gzipped the body: >>> res.content_encoding='gzip'
>>> # What language(s) are in the content: >>> res.content_language=['en']
>>> # Seldom used header that tells the client where the content >>> # is from: >>> res.content_location='http://localhost/foo'
>>> # Seldom used header that gives a hash of the body: >>> res.content_md5='big-hash'
>>> # Means we are serving bytes 0-500 inclusive, out of 1000 bytes total: >>> # you can also use the range setter shown earlier >>> res.content_range=(0, 501, 1000)
>>> # The length of the content; set automatically if you set >>> # res.body: >>> res.content_length=4
>>> # Used to indicate the current date as the server understands >>> # it: >>> res.date= datetime.now()
>>> # The etag: >>> res.etag='opaque-token' >>> # You can generate it from the body too: >>> res.md5_etag() >>> res.etag '1B2M2Y8AsgTpgAmY7PhCfg'
>>> # When this page should expire from a cache (Cache-Control >>> # often works better): >>> import time >>> res.expires= time.time()+ 60 *60 # 1 hour
>>> # When this was last modified, of course: >>> res.last_modified= datetime(2007,1,1, 12,0, tzinfo=UTC)
>>> # Used with 503 Service Unavailable to hint the client when to >>> # try again: >>> res.retry_after= 160
>>> # Indicate the server software: >>> res.server='WebOb/1.0'
>>> # Give a list of headers that the cache should vary on:
14 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
>>> res.vary=['Cookie']
Note in each case you can general set the header to a string to avoid any parsing, and set it to None to remove the header (or do something like del res.vary). In the case of date-related headers you can set the value to a datetime instance (ideally with a UTC timezone), a time tuple, an integer timestamp, or a properly-formatted string. After setting all these headers, here’s the result:
>>> for name, value in res.headerlist: ... print '%s: %s'% (name, value) Content-Type: application/atom+xml; charset=utf8; type=entry Location: http://localhost/foo Accept-Ranges: bytes Age: 120 Allow: GET, PUT Cache-Control: max-age=360, no-transform Content-Disposition: attachment; filename=foo.xml Content-Encoding: gzip Content-Language: en Content-Location: http://localhost/foo Content-MD5: big-hash Content-Range: bytes 0-500/1000 Content-Length: 4 Date: ... GMT ETag: ... Expires: ... GMT Last-Modified: Mon, 01 Jan 2007 12:00:00 GMT Retry-After: 160 Server: WebOb/1.0 Vary: Cookie
You can also set Cache-Control related attributes with req.cache_expires(seconds, **attrs), like:
>>> res= Response() >>> res.cache_expires(10) >>> res.headers['Cache-Control'] 'max-age=10' >>> res.cache_expires(0) >>> res.headers['Cache-Control'] 'max-age=0, must-revalidate, no-cache, no-store' >>> res.headers['Expires'] '... GMT'
You can also use the timedelta constants defined, e.g.:
>>> from webob import * >>> res= Response() >>> res.cache_expires(2*day+4*hour) >>> res.headers['Cache-Control'] 'max-age=187200'
Cookies
Cookies (and the Set-Cookie header) are handled with a couple methods. Most importantly:
1.3. Response 15 WebOb Documentation, Release 1.6.4
>>> res.set_cookie('key','value', max_age=360, path='/', ... domain='example.org', secure=True) >>> res.headers['Set-Cookie'] 'key=value; Domain=example.org; Max-Age=360; Path=/; expires=... GMT; secure' >>> # To delete a cookie previously set in the client: >>> res.delete_cookie('bad_cookie') >>> res.headers['Set-Cookie'] 'bad_cookie=; Max-Age=0; Path=/; expires=... GMT'
The only other real method of note (note that this does not delete the cookie from clients, only from the response object):
>>> res.unset_cookie('key') >>> res.unset_cookie('bad_cookie') >>> print res.headers.get('Set-Cookie') None
Binding a Request
You can bind a request (or request WSGI environ) to the response object. This is available through res.request or res.environ. This is currently only used in setting res.location, to make the location absolute if necessary.
Response as a WSGI application
A response is a WSGI application, in that you can do:
>>> req= Request.blank('/') >>> status, headers, app_iter= req.call_application(res)
A possible pattern for your application might be:
>>> def my_app(environ, start_response): ... req= Request(environ) ... res= Response() ... res.content_type='text/plain' ... parts=[] ... for name, value in sorted(req.environ.items()): ... parts.append('%s: %r'% (name, value)) ... res.body=' \n'.join(parts) ... return res(environ, start_response) >>> req= Request.blank('/') >>> res= req.get_response(my_app) >>> print res 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: ...
HTTP_HOST: 'localhost:80' PATH_INFO: '/' QUERY_STRING: '' REQUEST_METHOD: 'GET' SCRIPT_NAME: '' SERVER_NAME: 'localhost' SERVER_PORT: '80' SERVER_PROTOCOL: 'HTTP/1.0'
16 Chapter 1. WebOb Reference WebOb Documentation, Release 1.6.4
wsgi.errors:
Exceptions
In addition to Request and Response objects, there are a set of Python exceptions for different HTTP responses (3xx, 4xx, 5xx codes). These provide a simple way to provide these non-200 response. A very simple body is provided.
>>> from webob.exc import * >>> exc= HTTPTemporaryRedirect(location='foo') >>> req= Request.blank('/path/to/something') >>> print str(req.get_response(exc)).strip() 307 Temporary Redirect Location: http://localhost/path/to/foo Content-Length: 126 Content-Type: text/plain; charset=UTF-8
307 Temporary Redirect
The resource has been moved to http://localhost/path/to/foo; you should be redirected
˓→automatically.
Note that only if there’s an Accept: text/html header in the request will an HTML response be given:
>>> req.accept+='text/html' >>> print str(req.get_response(exc)).strip() 307 Temporary Redirect Location: http://localhost/path/to/foo Content-Length: 270 Content-Type: text/html; charset=UTF-8
307 Temporary Redirect
The resource has been moved to http://˓→localhost/path/to/foo
; you should be redirected automatically.
This is taken from paste.httpexceptions, and if you have Paste installed then these exceptions will be subclasses of the Paste exceptions.
1.4. Exceptions 17 WebOb Documentation, Release 1.6.4
Conditional WSGI Application
The Response object can handle your conditional responses for you, checking If-None-Match, If-Modified-Since, and Range/If-Range. To enable this you must create the response like Response(conditional_response=True), or make a sub- class like:
>>> class AppResponse(Response): ... default_content_type='text/html' ... default_conditional_response= True >>> res= AppResponse(body='0123456789', ... last_modified=datetime(2005,1,1, 12,0, tzinfo=UTC)) >>> req= Request.blank('/') >>> req.if_modified_since= datetime(2006,1,1, 12,0, tzinfo=UTC) >>> req.get_response(res)
>>> req.if_none_match=' *' >>> 'x' in req.if_none_match True >>> req.if_none_match= req.if_none_match >>> 'x' in req.if_none_match True >>> req.if_none_match= None >>> 'x' in req.if_none_match False >>> req.if_match= None >>> 'x' in req.if_match True >>> req.if_match= req.if_match >>> 'x' in req.if_match True >>> req.headers.get('If-Match') '*'
>>> del req.if_none_match
>>> req.range=(1,5) >>> result= req.get_response(res) >>> result.headers['content-range'] 'bytes 1-4/10' >>> result.body '1234'
18 Chapter 1. WebOb Reference CHAPTER 2
Differences Between WebOb and Other Systems
This document points out some of the API differences between the Request and Response object, and the objects in other systems.
Contents
• Differences Between WebOb and Other Systems – paste.wsgiwrappers and Pylons
* Request * Response – Django
* Request * QueryDict * Response * Response Subclasses – CherryPy/TurboGears
* Request * Response – Yaro
* Request – Werkzeug
* Request * Response
19 WebOb Documentation, Release 1.6.4
– Zope 3
* Request * Response – mod_python
* Request * Response – webapp Response – PHP
* $_POST, $_GET, $_FILES * $_COOKIES * $_SERVER, $_REQUEST, $_ENV * $HTTP_RAW_POST_DATA * The response paste.wsgiwrappers and Pylons
The Pylons request and response object are based on paste.wsgiwrappers.WSGIRequest and WSGIResponse There is no concept of defaults in WebOb. In Paste/Pylons these serve as threadlocal settings that control certain policies on the request and response object. In WebOb you should make your own subclasses to control policy (though in many ways simply being explicit elsewhere removes the need for this policy).
Request body: This is a file-like object in WSGIRequest. In WebOb it is a string (to match Response.body) and the file-like object is available through req.body_file languages(): This is available through req.accept_language, particularly req.accept_language. best_match(supported_languages) match_accept(mimetypes): This is available through req.accept.first_match(mimetypes); or if you trust the client’s quality ratings, you can use req.accept.best_match(mimetypes) errors: This controls how unicode decode errors are handled; it is now named unicode_errors There are also many extra methods and attributes on WebOb Request objects.
Response determine_charset(): Is now available as res.charset has_header(header): Should be done with header in res.headers get_content() and wsgi_response(): These are gone; you should use res.body or res(environ, start_response)
20 Chapter 2. Differences Between WebOb and Other Systems WebOb Documentation, Release 1.6.4
write(content): Available in res.body_file.write(content). flush() and tell(): Not available. There are also many extra methods and attributes on WebOb Response objects.
Django
This is a quick summary from reading the Django documentation.
Request
encoding: Is req.charset REQUEST: Is req.params FILES: File uploads are cgi.FieldStorage objects directly in res.POST META: Is req.environ user: No equivalent (too connected to application model for WebOb). There is req.remote_user, which is only ever a string. session: No equivalent raw_post_data: Available with req.body __getitem__(key): You have to use req.params is_secure(): No equivalent; you could use req.scheme == 'https'.
QueryDict
QueryDict is the way Django represents the multi-key dictionary-like objects that are request variables (query string and POST body variables). The equivalent in WebOb is MultiDict. Mutability: WebOb dictionaries are sometimes mutable (req.GET is, req.params is not) Ordering: I believe Django does not order the keys fully; MultiDict is a full ordering. Methods that iterate over the parameters iterate over keys in their order in the original request. keys(), items(), values() (plus iter*): These return all values in MultiDict, but only the last value for a QueryDict. That is, given a=1&a=2 with MultiDict d.items() returns [('a', '1'), ('a', '2')], but QueryDict returns [('a', '1')] getlist(key): Available as d.getall(key) setlist(key): No direct equivalent appendlist(key, value): Available as d.add(key, value) setlistdefault(key, default_list): No direct equivalent lists(): Is d.dict_of_lists() The MultiDict object has a d.getone(key) method, that raises KeyError if there is not exactly one key. There is a method d.mixed() which returns a version where values are lists if there are multiple values for a list. This is similar to how many cgi-based request forms are represented.
2.2. Django 21 WebOb Documentation, Release 1.6.4
Response
Constructor: Somewhat different. WebOb takes any keyword arguments as attribute assignments. Django only takes a couple arguments. The mimetype argument is content_type, and content_type is the entire Content-Type header (including charset). dictionary-like: The Django response object is somewhat dictionary-like, setting headers. The equivalent dictionary- like object is res.headers. In WebOb this is a MultiDict. has_header(header): Use header in res.headers flush(), tell(): Not available content: Use res.body for the str value, res.text for the unicode value
Response Subclasses
These are generally like webob.exc objects. HttpResponseNotModified is HTTPNotModified; this nam- ing translation generally works.
CherryPy/TurboGears
The CherryPy request object is also used by TurboGears 1.x.
Request app: No equivalent base: req.application_url close(): No equivalent closed: No equivalent config: No equivalent cookie: A SimpleCookie object in CherryPy; a dictionary in WebOb (SimpleCookie can represent cookie parameters, but cookie parameters are only sent with responses not requests) dispatch: No equivalent (this is the object dispatcher in CherryPy). error_page, error_response, handle_error: No equivalent get_resource(): Similar to req.get_response(app) handler: No equivalent headers, header_list: The WSGI environment represents headers as a dictionary, available through req. headers (no list form is available in the request). hooks: No equivalent local: No equivalent methods_with_bodies: This represents methods where CherryPy will automatically try to read the request body. WebOb lazily reads POST requests with the correct content type, and no other bodies. namespaces: No equivalent protocol: As req.environ['SERVER_PROTOCOL']
22 Chapter 2. Differences Between WebOb and Other Systems WebOb Documentation, Release 1.6.4
query_string: As req.query_string remote: remote.ip is like req.remote_addr. remote.port is not available. remote.name is in req. environ.get('REMOTE_HOST') request_line: No equivalent respond(): A method that is somewhat similar to req.get_response(). rfile: req.body_file run: No equivalent server_protocol: As req.environ['SERVER_PROTOCOL'] show_tracebacks: No equivalent throw_errors: No equivalent throws: No equivalent toolmaps: No equivalent wsgi_environ: As req.environ
Response
From information from the wiki. body: This is an iterable in CherryPy, a string in WebOb; res.app_iter gives an iterable in WebOb. check_timeout: No equivalent collapse_body(): This turns a stream/iterator body into a single string. Accessing res.body will do this automatically. cookie: Accessible through res.set_cookie(...), res.delete_cookie, res.unset_cookie() finalize(): No equivalent header_list: In res.headerlist stream: This can make CherryPy stream the response body out directory. There is direct no equivalent; you can use a dynamically generated iterator to do something similar. time: No equivalent timed_out: No equivalent
Yaro
Yaro is a small wrapper around the WSGI environment, much like WebOb in scope. The WebOb objects have many more methods and attributes. The Yaro Response object is a much smaller subset of WebOb’s Response.
Request
query: As req.GET form: As req.POST
2.4. Yaro 23 WebOb Documentation, Release 1.6.4
cookie: A SimpleCookie object in Yaro; a dictionary in WebOb (SimpleCookie can represent cookie param- eters, but cookie parameters are only sent with responses not requests) uri: Returns a URI object, no equivalent (only string URIs available). redirect: Not available (response-related). webob.exc.HTTPFound() can be useful here. forward(yaroapp), wsgi_forward(wsgiapp): Available with req.get_response(app) and req. call_application(app). In both cases it is a WSGI application in WebOb, there is no special kind of communication; req.call_application() just returns a webob.Response object. res: The request object in WebOb may have a req.response attribute.
Werkzeug
An offshoot of Pocoo, this library is based around WSGI, similar to Paste and Yaro. This is taken from the wrapper documentation.
Request path: As req.path_info args: As req.GET form: As req.POST values: As req.params files: In req.POST (as FieldStorage objects) data: In req.body_file
Response response: In res.body (settable as res.body or res.app_iter) status: In res.status_code mimetype: In res.content_type
Zope 3
From the Zope 3 interfaces for the Request and Response.
Request locale, setupLocale(): This is not fully calculated, but information is available in req. accept_languages. principal, setPrincipal(principal): req.remote_user gives the username, but there is no standard place for a user object. publication, setPublication(), These are associated with the object publishing system in Zope. This kind of publishing system is outside the scope of WebOb.
24 Chapter 2. Differences Between WebOb and Other Systems WebOb Documentation, Release 1.6.4
traverse(object), getTraversalStack(), setTraversalStack(): These all relate to traversal, which is part of the publishing system. processInputs(), setPathSuffix(steps): Also associated with traversal and preparing the request. environment: In req.environ bodyStream: In req.body_file interaction: This is the security context for the request; all the possible participants or principals in the request. There’s no equivalent. annotations: Extra information associated with the request. This would generally go in custom keys of req. environ, or if you set attributes those attributes are stored in req.environ['webob.adhoc_attrs']. debug: There is no standard debug flag for WebOb. __getitem__(key), get(key), etc: These treat the request like a dictionary, which WebOb does not do. They seem to take values from the environment, not parameters. Also on the Zope request object is items(), __contains__(key), __iter__(), keys(), __len__(), values(). getPositionalArguments(): I’m not sure what the equivalent would be, as there are no positional arguments during instantiation (it doesn’t fit into WSGI). Maybe wsgiorg.urlvars? retry(), supportsRetry(): Creates a new request that can be used to retry a request. Similar to req. copy(). close(), hold(obj): This closes resources associated with the request, including any “held” objects. There’s nothing similar.
Response
authUser: Not sure what this is or does. reset(): No direct equivalent; you’d have to do res.headers = []; res.body = ''; res.status = 200 setCookie(name, value, **kw): Is res.set_cookie(...). getCookie(name): No equivalent. Hm. expireCookie(name): Is res.delete_cookie(name). appendToCookie(name, value): This appends the value to any existing cookie (separating values with a colon). WebOb does not do this. setStatus(status): Availble by setting res.status (can be set to an integer or a string of “code reason”). getHeader(name, default=None): Is res.headers.get(name). getStatus(): Is res.status_code (or res.status to include reason) addHeader(name, value): Is res.headers.add(name, value) (in Zope and WebOb, this does not clobber any previous value). getHeaders(): Is res.headerlist. setHeader(name, value): Is res.headers[name] = value. getStatusString(): Is res.status. consumeBody(): This consumes any non-string body to turn the body into a single string. Any access to res. body will do this (e.g., when you have set the res.app_iter). internalError(): This is available with webob.exc.HTTP*().
2.6. Zope 3 25 WebOb Documentation, Release 1.6.4
handleException(exc_info): This is provided with a tool like paste.exceptions. consumeBodyIter(): This returns the iterable for the body, even if the body was a string. Anytime you access res.app_iter you will get an iterable. res.body and res.app_iter can be interchanged and accessed as many times as you want, unlike the Zope equivalents. setResult(result): You can achieve the same thing through res.body = result, or res.app_iter = result. res.body accepts None, a unicode string (if you have set a charset) or a normal string. res. app_iter only accepts None and an interable. You can’t update all of a response with one call. Like in Zope, WebOb updates Content-Length. Unlike Zope, it does not automatically calculate a charset. mod_python
Some key attributes from the mod_python request object.
Request req.uri: In req.path. req.user: In req.remote_user. req.get_remote_host(): In req.environ['REMOTE_ADDR'] or req.remote_addr. req.headers_in.get('referer'): In req.headers.get('referer') or req.referer (same pattern for other request headers, presumably).
Response util.redirect or req.status = apache.HTTP_MOVED_TEMPORARILY:
from webob.exc import HTTPTemporaryRedirect exc= HTTPTemporaryRedirect(location=url) return exc(environ, start_response)
req.content_type = "application/x-csv" and req.headers_out. add('Content-Disposition', 'attachment;filename=somefile.csv'):
res= req.ResponseClass() res.content_type='application/x-csv' res.headers.add('Content-Disposition','attachment;filename=somefile.csv') return res(environ, start_response)
webapp Response
The Google App Engine webapp framework uses the WebOb Request object, but does not use its Response object. The constructor for webapp.Response does not take any arguments. The response is created by the framework, so you don’t use it like return Response(...), instead you use self.response. Also the response object automatically has Cache-Control: no-cache set, while the WebOb response does not set any cache headers. resp.set_status(code, message=None): This is handled by setting the resp.status attribute. resp.clear(): You’d do resp.body = ""
26 Chapter 2. Differences Between WebOb and Other Systems WebOb Documentation, Release 1.6.4
resp.wsgi_write(start_response): This writes the response using the start_response callback, and using the start_response writer. The WebOb response object is called as a WSGI app (resp(environ, start_response)) to do the equivalent. resp.out.write(text): This writes to an internal StringIO instance of the response. This uses the ability of the standard StringIO object to hold either unicode or str text, and so long as you are always consistent it will encode your content (but it does not respect your preferred encoding, it always uses UTF-8). The WebOb method resp.write(text) is basically equivalent, and also accepts unicode (using resp.charset for the encoding). You can also write to resp.body_file, but it does not allow unicode. Besides exposing a .headers attribute (based on wsgiref.headers.Headers) there is no other API for the webapp response object. This means the response lacks: • A usefully readable body or status. • A useful constructor that makes it easy to treat responses like objects. • Providing a non-string app_iter for the body (like a generator). • Parsing of the Content-Type charset. • Getter/setters for parsed forms of headers, specifically cache_control and last_modified. • The cache_expires method • set_cookie, delete_cookie, and unset_cookie. Instead you have to simply manually set the Set- Cookie header. • encode_content and decode_content for handling gzip encoding. • md5_etag() for generating an etag from the body. • Conditional responses that will return 304 based on the response and request headers. • The ability to serve Range request automatically.
PHP
PHP does not have anything really resembling a request and response object. Instead these are encoded in a set of global objects for the request and functions for the response.
$_POST, $_GET, $_FILES
These represent req.POST and req.GET. PHP uses the variable names to tell whether a variable can hold multiple values. For instance $_POST['name[]'], which will be an array. In WebOb any variable can have multiple values, and you can get these through req.POST. getall('name'). The files in $_FILES are simply in req.POST in WebOb, as FieldStorage instances.
$_COOKIES
This is in req.cookies.
2.9. PHP 27 WebOb Documentation, Release 1.6.4
$_SERVER, $_REQUEST, $_ENV
These are all in req.environ. These are not split up like they are in PHP, it’s all just one dictionary. Everything that would typically be in $_ENV is technically optional, and outside of a couple CGI-standard keys in $_SERVER most of those are also optional, but it is common for WSGI servers to populate the request with similar information as PHP.
$HTTP_RAW_POST_DATA
This contains the unparsed data in the request body. This is in req.body.
The response
Response headers in PHP are sent with header("Header-Name: value"). In WebOb there is a dictio- nary in resp.headers that can have values set; the headers aren’t actually sent until you send the response. You can add headers without overwriting (the equivalent of header("...", false)) with resp.headers. add('Header-Name', 'value'). The status in PHP is sent with http_send_status(code). In WebOb this is resp.status = code. The body in PHP is sent implicitly through the rendering of the PHP body (or with echo or any other functions that send output).
28 Chapter 2. Differences Between WebOb and Other Systems CHAPTER 3
License
Copyright (c) 2007 Ian Bicking and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documen- tation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PAR- TICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFT- WARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 WebOb Documentation, Release 1.6.4
30 Chapter 3. License CHAPTER 4
API Documentation
Reference material for every public API exposed by WebOb: webob.client – Send WSGI requests over HTTP
Client class webob.client.SendRequest(HTTPConnection=
31 WebOb Documentation, Release 1.6.4
webob.cookies – Cookies
Cookies
class webob.cookies.CookieProfile(cookie_name, secure=False, max_age=None, httponly=None, path=’/’, domains=None, serializer=None) A helper class that helps bring some sanity to the insanity that is cookie handling. The helper is capable of generating multiple cookies if necessary to support subdomains and parent domains. cookie_name The name of the cookie used for sessioning. Default: 'session'. max_age The maximum age of the cookie used for sessioning (in seconds). Default: None (browser scope). secure The ‘secure’ flag of the session cookie. Default: False. httponly Hide the cookie from Javascript by setting the ‘HttpOnly’ flag of the session cookie. Default: False. path The path used for the session cookie. Default: '/'. domains The domain(s) used for the session cookie. Default: None (no domain). Can be passed an iterable containing multiple domains, this will set multiple cookies one for each domain. serializer An object with two methods: loads and dumps. The loads method should accept a bytestring and return a Python object. The dumps method should accept a Python object and return bytes. A ValueError should be raised for malformed inputs. Default: None, which will use a derivation of json.dumps() and json.loads(). bind(request) Bind a request to a copy of this instance and return it get_headers(value, domains=
32 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
hashalg The HMAC digest algorithm to use for signing. The algorithm must be supported by the hashlib library. Default: 'sha512'. cookie_name The name of the cookie used for sessioning. Default: 'session'. max_age The maximum age of the cookie used for sessioning (in seconds). Default: None (browser scope). secure The ‘secure’ flag of the session cookie. Default: False. httponly Hide the cookie from Javascript by setting the ‘HttpOnly’ flag of the session cookie. Default: False. path The path used for the session cookie. Default: '/'. domains The domain(s) used for the session cookie. Default: None (no domain). Can be passed an iterable containing multiple domains, this will set multiple cookies one for each domain. serializer An object with two methods: loads‘ and dumps. The loads method should accept bytes and return a Python object. The dumps method should accept a Python object and return bytes. A ValueError should be raised for malformed inputs. Default: None`, which will use a derivation of :func:`json.dumps` and ``json.loads. bind(request) Bind a request to a copy of this instance and return it class webob.cookies.SignedSerializer(secret, salt, hashalg=’sha512’, serializer=None) A helper to cryptographically sign arbitrary content using HMAC. The serializer accepts arbitrary functions for performing the actual serialization and deserialization. secret A string which is used to sign the cookie. The secret should be at least as long as the block size of the selected hash algorithm. For sha512 this would mean a 128 bit (64 character) secret. salt A namespace to avoid collisions between different uses of a shared secret. hashalg The HMAC digest algorithm to use for signing. The algorithm must be supported by the hashlib library. Default: 'sha512'. serializer An object with two methods: loads‘ and dumps. The loads method should accept bytes and return a Python object. The dumps method should accept a Python object and return bytes. A ValueError should be raised for malformed inputs. Default: None`, which will use a derivation of :func:`json.dumps` and ``json.loads. dumps(appstruct) Given an appstruct, serialize and sign the data. Returns a bytestring. loads(bstruct) Given a bstruct (a bytestring), verify the signature and then deserialize and return the deserialized value. A ValueError will be raised if the signature fails to validate. class webob.cookies.JSONSerializer A serializer which uses json.dumps‘ and json.loads webob.cookies.make_cookie(name, value, max_age=None, path=’/’, domain=None, secure=False, httponly=False, comment=None) Generate a cookie value. If value is None, generate a cookie value with an expiration date in the past
4.2. webob.cookies – Cookies 33 WebOb Documentation, Release 1.6.4 webob.dec – WSGIfy decorator
Decorators to wrap functions to make them WSGI applications. The main decorator wsgify turns a function into a WSGI application (while also allowing normal calling of the method with an instantiated request).
Decorator class webob.dec.wsgify(func=None, RequestClass=None, args=(), kwargs=None, middle- ware_wraps=None) Turns a request-taking, response-returning function into a WSGI app You can use this like:
@wsgify def myfunc(req): return webob.Response('hey there')
With that myfunc will be a WSGI application, callable like app_iter = myfunc(environ, start_response). You can also call it like normal, e.g., resp = myfunc(req). (You can also wrap methods, like def myfunc(self, req).) If you raise exceptions from webob.exc they will be turned into WSGI responses. There are also several parameters you can use to customize the decorator. Most notably, you can use a webob. Request subclass, like:
class MyRequest(webob.Request): @property def is_local(self): return self.remote_addr =='127.0.0.1' @wsgify(RequestClass=MyRequest) def myfunc(req): if req.is_local: return Response('hi!') else: raise webob.exc.HTTPForbidden
Another customization you can add is to add args (positional arguments) or kwargs (of course, keyword ar- guments). While generally not that useful, you can use this to create multiple WSGI apps from one function, like:
import simplejson def serve_json(req, json_obj): return Response(json.dumps(json_obj), content_type='application/json')
serve_ob1= wsgify(serve_json, args=(ob1,)) serve_ob2= wsgify(serve_json, args=(ob2,))
You can return several things from a function: •A webob.Response object (or subclass) •Any WSGI application •None, and then req.response will be used (a pre-instantiated Response object)
34 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
•A string, which will be written to req.response and then that response will be used. •Raise an exception from webob.exc Also see wsgify.middleware() for a way to make middleware. You can also subclass this decorator; the most useful things to do in a subclass would be to change RequestClass or override call_func (e.g., to add req.urlvars as keyword arguments to the function). RequestClass alias of Request call_func(req, *args, **kwargs) Call the wrapped function; override this in a subclass to change how the function is called. clone(func=None, **kw) Creates a copy/clone of this object, but with some parameters rebound get(url, **kw) Run a GET request on this application, returning a Response. This creates a request object using the given URL, and any other keyword arguments are set on the request object (e.g., last_modified=datetime.now()).
resp= myapp.get('/article?id=10')
classmethod middleware(middle_func=None, app=None, **kw) Creates middleware Use this like:
@wsgify.middleware def restrict_ip(req, app, ips): if req.remote_addr not in ips: raise webob.exc.HTTPForbidden('Bad IP: %s'% req.remote_addr) return app
@wsgify def app(req): return 'hi'
wrapped= restrict_ip(app, ips=['127.0.0.1'])
Or if you want to write output-rewriting middleware:
@wsgify.middleware def all_caps(req, app): resp= req.get_response(app) resp.body= resp.body.upper() return resp
wrapped= all_caps(app)
Note that you must call req.get_response(app) to get a WebOb response object. If you are not modifying the output, you can just return the app. As you can see, this method doesn’t actually create an application, but creates “middleware” that can be bound to an application, along with “configuration” (that is, any other keyword arguments you pass when binding the application). post(url, POST=None, **kw) Run a POST request on this application, returning a Response.
4.3. webob.dec – WSGIfy decorator 35 WebOb Documentation, Release 1.6.4
The second argument (POST) can be the request body (a string), or a dictionary or list of two-tuples, that give the POST body.
resp= myapp.post('/article/new', dict(title='My Day', content='I ate a sandwich'))
request(url, **kw) Run a request on this application, returning a Response. This can be used for DELETE, PUT, etc requests. E.g.:
resp= myapp.request('/article/1', method='PUT', body='New article') webob.exc – WebOb Exceptions
This module processes Python exceptions that relate to HTTP exceptions by defining a set of exceptions, all subclasses of HTTPException. Each exception, in addition to being a Python exception that can be raised and caught, is also a WSGI application and webob.Response object. This module defines exceptions according to RFC 20681 : codes with 100-300 are not really errors; 400’s are client er- rors, and 500’s are server errors. According to the WSGI specification2 , the application can call start_response more then once only under two conditions: (a) the response has not yet been sent, or (b) if the second and sub- sequent invocations of start_response have a valid exc_info argument obtained from sys.exc_info(). The WSGI specification then requires the server or gateway to handle the case where content has been sent and then an exception was encountered. Exception HTTPException HTTPOk • 200 - HTTPOk • 201 - HTTPCreated • 202 - HTTPAccepted • 203 - HTTPNonAuthoritativeInformation • 204 - HTTPNoContent • 205 - HTTPResetContent • 206 - HTTPPartialContent HTTPRedirection • 300 - HTTPMultipleChoices • 301 - HTTPMovedPermanently • 302 - HTTPFound • 303 - HTTPSeeOther • 304 - HTTPNotModified • 305 - HTTPUseProxy
1 http://www.python.org/peps/pep-0333.html#error-handling 2 http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5
36 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
• 307 - HTTPTemporaryRedirect • 308 - HTTPPermanentRedirect HTTPError HTTPClientError • 400 - HTTPBadRequest • 401 - HTTPUnauthorized • 402 - HTTPPaymentRequired • 403 - HTTPForbidden • 404 - HTTPNotFound • 405 - HTTPMethodNotAllowed • 406 - HTTPNotAcceptable • 407 - HTTPProxyAuthenticationRequired • 408 - HTTPRequestTimeout • 409 - HTTPConflict • 410 - HTTPGone • 411 - HTTPLengthRequired • 412 - HTTPPreconditionFailed • 413 - HTTPRequestEntityTooLarge • 414 - HTTPRequestURITooLong • 415 - HTTPUnsupportedMediaType • 416 - HTTPRequestRangeNotSatisfiable • 417 - HTTPExpectationFailed • 422 - HTTPUnprocessableEntity • 423 - HTTPLocked • 424 - HTTPFailedDependency • 428 - HTTPPreconditionRequired • 429 - HTTPTooManyRequests • 431 - HTTPRequestHeaderFieldsTooLarge • 451 - HTTPUnavailableForLegalReasons HTTPServerError • 500 - HTTPInternalServerError • 501 - HTTPNotImplemented • 502 - HTTPBadGateway • 503 - HTTPServiceUnavailable • 504 - HTTPGatewayTimeout • 505 - HTTPVersionNotSupported
4.4. webob.exc – WebOb Exceptions 37 WebOb Documentation, Release 1.6.4
• 511 - HTTPNetworkAuthenticationRequired
Usage notes
The HTTPException class is complicated by 4 factors: 1. The content given to the exception may either be plain-text or as html-text. 2. The template may want to have string-substitutions taken from the current environ or values from incoming headers. This is especially troublesome due to case sensitivity. 3. The final output may either be text/plain or text/html mime-type as requested by the client application. 4. Each exception has a default explanation, but those who raise exceptions may want to provide additional detail. Subclass attributes and call parameters are designed to provide an easier path through the complications. Attributes: code the HTTP status code for the exception title remainder of the status line (stuff after the code) explanation a plain-text explanation of the error message that is not subject to environment or header substitutions; it is accessible in the template via %(explanation)s detail a plain-text message customization that is not subject to environment or header substitutions; accessible in the template via %(detail)s body_template a content fragment (in HTML) used for environment and header substitution; the default template includes both the explanation and further detail provided in the message Parameters: detail a plain-text override of the default detail headers a list of (k,v) header pairs comment a plain-text additional information which is usually stripped/hidden for end-users body_template a string.Template object containing a content fragment in HTML that frames the explanation and further detail To override the template (which is HTML content) or the plain-text explanation, one must subclass the given excep- tion; or customize it after it has been created. This particular breakdown of a message into explanation, detail and template allows both the creation of plain-text and html messages for various clients as well as error-free substitution of environment variables and headers. The subclasses of _HTTPMove (HTTPMultipleChoices, HTTPMovedPermanently, HTTPFound, HTTPSeeOther, HTTPUseProxy and HTTPTemporaryRedirect) are redirections that require a Location field. Reflecting this, these subclasses have two additional keyword arguments: location and add_slash. Parameters: location to set the location immediately add_slash set to True to redirect to the same URL as the request, except with a / appended Relative URLs in the location will be resolved to absolute. References:
38 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
HTTP Exceptions
exception webob.exc.HTTPException(message, wsgi_response) exception webob.exc.WSGIHTTPException(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) exception webob.exc.HTTPError(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) base class for status codes in the 400’s and 500’s This is an exception which indicates that an error has occurred, and that any work in progress should not be committed. These are typically results in the 400’s and 500’s. exception webob.exc.HTTPRedirection(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) base class for 300’s status code (redirections) This is an abstract base class for 3xx redirection. It indicates that further action needs to be taken by the user agent in order to fulfill the request. It does not necessarly signal an error condition. exception webob.exc.HTTPOk(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) Base class for the 200’s status code (successful responses) code: 200, title: OK exception webob.exc.HTTPCreated(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk This indicates that request has been fulfilled and resulted in a new resource being created. code: 201, title: Created exception webob.exc.HTTPAccepted(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk This indicates that the request has been accepted for processing, but the processing has not been completed. code: 202, title: Accepted exception webob.exc.HTTPNonAuthoritativeInformation(detail=None, headers=None, com- ment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk This indicates that the returned metainformation in the entity-header is not the definitive set as available from the origin server, but is gathered from a local or a third-party copy. code: 203, title: Non-Authoritative Information exception webob.exc.HTTPNoContent(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk This indicates that the server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. code: 204, title: No Content exception webob.exc.HTTPResetContent(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk
4.4. webob.exc – WebOb Exceptions 39 WebOb Documentation, Release 1.6.4
This indicates that the the server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent. code: 205, title: Reset Content exception webob.exc.HTTPPartialContent(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPOk This indicates that the server has fulfilled the partial GET request for the resource. code: 206, title: Partial Content exception webob.exc._HTTPMove(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) redirections which require a Location field Since a ‘Location’ header is a required attribute of 301, 302, 303, 305, 307 and 308 (but not 304), this base class provides the mechanics to make this easy. You can provide a location keyword argument to set the location immediately. You may also give add_slash=True if you want to redirect to the same URL as the request, except with a / added to the end. Relative URLs in the location will be resolved to absolute. exception webob.exc.HTTPMultipleChoices(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the requested resource corresponds to any one of a set of representations, each with its own specific location, and agent-driven negotiation information is being provided so that the user can select a preferred representation and redirect its request to that location. code: 300, title: Multiple Choices exception webob.exc.HTTPMovedPermanently(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the requested resource has been assigned a new permanent URI and any future references to this resource SHOULD use one of the returned URIs. code: 301, title: Moved Permanently exception webob.exc.HTTPFound(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the requested resource resides temporarily under a different URI. code: 302, title: Found exception webob.exc.HTTPSeeOther(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the response to the request can be found under a different URI and SHOULD be retrieved using a GET method on that resource. code: 303, title: See Other
40 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
exception webob.exc.HTTPNotModified(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPRedirection This indicates that if the client has performed a conditional GET request and access is allowed, but the document has not been modified, the server SHOULD respond with this status code. code: 304, title: Not Modified exception webob.exc.HTTPUseProxy(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the requested resource MUST be accessed through the proxy given by the Location field. code: 305, title: Use Proxy exception webob.exc.HTTPTemporaryRedirect(detail=None, headers=None, comment=None, body_template=None, location=None, add_slash=False) subclass of _HTTPMove This indicates that the requested resource resides temporarily under a different URI. code: 307, title: Temporary Redirect exception webob.exc.HTTPClientError(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) base class for the 400’s, where the client is in error This is an error condition in which the client is presumed to be in-error. This is an expected problem, and thus is not considered a bug. A server-side traceback is not warranted. Unless specialized, this is a ‘400 Bad Request’ code: 400, title: Bad Request exception webob.exc.HTTPBadRequest(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) exception webob.exc.HTTPUnauthorized(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the request requires user authentication. code: 401, title: Unauthorized exception webob.exc.HTTPPaymentRequired(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError code: 402, title: Payment Required exception webob.exc.HTTPForbidden(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server understood the request, but is refusing to fulfill it. code: 403, title: Forbidden exception webob.exc.HTTPNotFound(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server did not find anything matching the Request-URI. code: 404, title: Not Found
4.4. webob.exc – WebOb Exceptions 41 WebOb Documentation, Release 1.6.4 exception webob.exc.HTTPMethodNotAllowed(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the method specified in the Request-Line is not allowed for the resource identified by the Request-URI. code: 405, title: Method Not Allowed exception webob.exc.HTTPNotAcceptable(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates the resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request. code: 406, title: Not Acceptable exception webob.exc.HTTPProxyAuthenticationRequired(detail=None, headers=None, com- ment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This is similar to 401, but indicates that the client must first authenticate itself with the proxy. code: 407, title: Proxy Authentication Required exception webob.exc.HTTPRequestTimeout(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the client did not produce a request within the time that the server was prepared to wait. code: 408, title: Request Timeout exception webob.exc.HTTPConflict(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the request could not be completed due to a conflict with the current state of the resource. code: 409, title: Conflict exception webob.exc.HTTPGone(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the requested resource is no longer available at the server and no forwarding address is known. code: 410, title: Gone exception webob.exc.HTTPLengthRequired(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the the server refuses to accept the request without a defined Content-Length. code: 411, title: Length Required exception webob.exc.HTTPPreconditionFailed(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the precondition given in one or more of the request-header fields evaluated to false when it was tested on the server.
42 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
code: 412, title: Precondition Failed exception webob.exc.HTTPRequestEntityTooLarge(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is refusing to process a request because the request entity is larger than the server is willing or able to process. code: 413, title: Request Entity Too Large exception webob.exc.HTTPRequestURITooLong(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is refusing to service the request because the Request-URI is longer than the server is willing to interpret. code: 414, title: Request-URI Too Long exception webob.exc.HTTPUnsupportedMediaType(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is refusing to service the request because the entity of the request is in a format not supported by the requested resource for the requested method. code: 415, title: Unsupported Media Type exception webob.exc.HTTPRequestRangeNotSatisfiable(detail=None, headers=None, com- ment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError The server SHOULD return a response with this status code if a request included a Range request-header field, and none of the range-specifier values in this field overlap the current extent of the selected resource, and the request did not include an If-Range request-header field. code: 416, title: Request Range Not Satisfiable exception webob.exc.HTTPExpectationFailed(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indidcates that the expectation given in an Expect request-header field could not be met by this server. code: 417, title: Expectation Failed exception webob.exc.HTTPUnprocessableEntity(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is unable to process the contained instructions. code: 422, title: Unprocessable Entity exception webob.exc.HTTPLocked(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the resource is locked.
4.4. webob.exc – WebOb Exceptions 43 WebOb Documentation, Release 1.6.4
code: 423, title: Locked exception webob.exc.HTTPFailedDependency(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the method could not be performed because the requested action depended on another action and that action failed. code: 424, title: Failed Dependency exception webob.exc.HTTPPreconditionRequired(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the origin server requires the request to be conditional. From RFC 6585, “Additional HTTP Status Codes”. code: 428, title: Precondition Required exception webob.exc.HTTPTooManyRequests(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the client has sent too many requests in a given amount of time. Useful for rate limiting. From RFC 6585, “Additional HTTP Status Codes”. code: 429, title: Too Many Requests exception webob.exc.HTTPRequestHeaderFieldsTooLarge(detail=None, headers=None, com- ment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is unwilling to process the request because its header fields are too large. The request may be resubmitted after reducing the size of the request header fields. From RFC 6585, “Additional HTTP Status Codes”. code: 431, title: Request Header Fields Too Large exception webob.exc.HTTPUnavailableForLegalReasons(detail=None, headers=None, com- ment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPClientError This indicates that the server is unable to process the request because of legal reasons, e.g. censorship or government-mandated blocked access. From the draft “A New HTTP Status Code for Legally-restricted Resources” by Tim Bray: http://tools.ietf.org/html/draft-tbray-http-legally-restricted-status-00 code: 451, title: Unavailable For Legal Reasons exception webob.exc.HTTPServerError(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) base class for the 500’s, where the server is in-error This is an error condition in which the server is presumed to be in-error. This is usually unexpected, and thus requires a traceback; ideally, opening a support ticket for the customer. Unless specialized, this is a ‘500 Internal Server Error’
44 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
exception webob.exc.HTTPInternalServerError(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) exception webob.exc.HTTPNotImplemented(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server does not support the functionality required to fulfill the request. code: 501, title: Not Implemented exception webob.exc.HTTPBadGateway(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server, while acting as a gateway or proxy, received an invalid response from the upstream server it accessed in attempting to fulfill the request. code: 502, title: Bad Gateway exception webob.exc.HTTPServiceUnavailable(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server is currently unable to handle the request due to a temporary overloading or main- tenance of the server. code: 503, title: Service Unavailable exception webob.exc.HTTPGatewayTimeout(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server, while acting as a gateway or proxy, did not receive a timely response from the upstream server specified by the URI (e.g. HTTP, FTP, LDAP) or some other auxiliary server (e.g. DNS) it needed to access in attempting to complete the request. code: 504, title: Gateway Timeout exception webob.exc.HTTPVersionNotSupported(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server does not support, or refuses to support, the HTTP protocol version that was used in the request message. code: 505, title: HTTP Version Not Supported exception webob.exc.HTTPInsufficientStorage(detail=None, headers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError This indicates that the server does not have enough space to save the resource. code: 507, title: Insufficient Storage exception webob.exc.HTTPNetworkAuthenticationRequired(detail=None, head- ers=None, comment=None, body_template=None, json_formatter=None, **kw) subclass of HTTPServerError
4.4. webob.exc – WebOb Exceptions 45 WebOb Documentation, Release 1.6.4
This indicates that the client needs to authenticate to gain network access. From RFC 6585, “Additional HTTP Status Codes”. code: 511, title: Network Authentication Required exception webob.exc.HTTPExceptionMiddleware(application) Middleware that catches exceptions in the sub-application. This does not catch exceptions in the app_iter; only during the initial calling of the application. This should be put very close to applications that might raise these exceptions. This should not be applied globally; letting expected exceptions raise through the WSGI stack is dangerous.
webob.multidict – multi-value dictionary object
Gives a multi-value dictionary object (MultiDict) plus several wrappers class webob.multidict.MultiDict(*args, **kw) An ordered dictionary that can have multiple values for each key. Adds the methods getall, getone, mixed and extend and add to the normal dictionary interface. add(key, value) Add the key and value, not overwriting any previous value. dict_of_lists() Returns a dictionary where each key is associated with a list of values. classmethod from_fieldstorage(fs) Create a dict from a cgi.FieldStorage instance get(k[, d ]) → D[k] if k in D, else d. d defaults to None. getall(key) Return a list of all values matching the key (may be an empty list) getone(key) Get one value matching the key, raising a KeyError if multiple values were found. mixed() Returns a dictionary where the values are either single values, or a list of values when a key/value appears more than once in this dictionary. This is similar to the kind of dictionary often used to represent the variables in a web request. classmethod view_list(lst) Create a dict that is a view on the given list class webob.multidict.NestedMultiDict(*dicts) Wraps several MultiDict objects, treating it as one large MultiDict class webob.multidict.NoVars(reason=None) Represents no variables; used when no variables are applicable. This is read-only
46 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4 webob.request – Request
Request class webob.request.Request(environ, charset=None, unicode_errors=None, de- code_param_names=None, **kw) The default request implementation class webob.request.BaseRequest(environ, charset=None, unicode_errors=None, de- code_param_names=None, **kw)
GET Return a MultiDict containing all the variables from the QUERY_STRING. POST Return a MultiDict containing all the variables from a form request. Returns an empty dict-like object for non-form requests. Form requests are typically POST requests, however PUT & PATCH requests with an appropriate Content- Type are also supported. ResponseClass alias of Response accept Gets and sets the Accept header (HTTP spec section 14.1). accept_charset Gets and sets the Accept-Charset header (HTTP spec section 14.2). accept_encoding Gets and sets the Accept-Encoding header (HTTP spec section 14.3). accept_language Gets and sets the Accept-Language header (HTTP spec section 14.4). application_url The URL including SCRIPT_NAME (no PATH_INFO or query string) as_bytes(skip_body=False) Return HTTP bytes representing this request. If skip_body is True, exclude the body. If skip_body is an integer larger than one, skip body only if its length is bigger than that number. authorization Gets and sets the Authorization header (HTTP spec section 14.8). Converts it using parse_auth and serialize_auth. classmethod blank(path, environ=None, base_url=None, headers=None, POST=None, **kw) Create a blank request environ (and Request wrapper) with the given path (path should be urlencoded), and any keys from environ. The path will become path_info, with any query string split off and used. All necessary keys will be added to the environ, but the values you pass in will take precedence. If you pass in base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will be filled in from that value. Any extra keyword will be passed to __init__. body Return the content of the request body.
4.6. webob.request – Request 47 WebOb Documentation, Release 1.6.4
body_file Input stream of the request (wsgi.input). Setting this property resets the content_length and seekable flag (unlike setting req.body_file_raw). body_file_raw Gets and sets the wsgi.input key in the environment. body_file_seekable Get the body of the request (wsgi.input) as a seekable file-like object. Middleware and routing applications should use this attribute over .body_file. If you access this value, CONTENT_LENGTH will also be updated. cache_control Get/set/modify the Cache-Control header (HTTP spec section 14.9) call_application(application, catch_exc_info=False) Call the given WSGI application, returning (status_string, headerlist, app_iter) Be sure to call app_iter.close() if it’s there. If catch_exc_info is true, then returns (status_string, headerlist, app_iter, exc_info), where the fourth item may be None, but won’t be if there was an exception. If you don’t do this and there was an exception, the exception will be raised directly. client_addr The effective client IP address as a string. If the HTTP_X_FORWARDED_FOR header exists in the WSGI environ, this attribute returns the client IP address present in that header (e.g. if the header value is 192. 168.1.1, 192.168.1.2, the value will be 192.168.1.1). If no HTTP_X_FORWARDED_FOR header is present in the environ at all, this attribute will return the value of the REMOTE_ADDR header. If the REMOTE_ADDR header is unset, this attribute will return the value None.
Warning: It is possible for user agents to put someone else’s IP or just any string in HTTP_X_FORWARDED_FOR as it is a normal HTTP header. Forward proxies can also provide in- correct values (private IP addresses etc). You cannot “blindly” trust the result of this method to provide you with valid data unless you’re certain that HTTP_X_FORWARDED_FOR has the correct values. The WSGI server must be behind a trusted proxy for this to be true.
content_length Gets and sets the Content-Length header (HTTP spec section 14.13). Converts it using int. content_type Return the content type, but leaving off any parameters (like charset, but also things like the type in application/atom+xml; type=entry) If you set this property, you can include parameters, or if you don’t include any parameters in the value then existing parameters will be preserved. cookies Return a dictionary of cookies as found in the request. copy() Copy the request and environment object. This only does a shallow copy, except of wsgi.input copy_body() Copies the body, in cases where it might be shared with another request object and that is not desired. This copies the body in-place, either into a BytesIO object or a temporary file.
48 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
copy_get() Copies the request and environment object, but turning this request into a GET along the way. If this was a POST request (or any other verb) then it becomes GET, and the request body is thrown away. date Gets and sets the Date header (HTTP spec section 14.8). Converts it using HTTP date. domain Returns the domain portion of the host value. Equivalent to:
domain= request.host if ':' in domain: domain= domain.split(':',1)[0]
This will be equivalent to the domain portion of the HTTP_HOST value in the environment if it exists, or the SERVER_NAME value in the environment if it doesn’t. For example, if the environment contains an HTTP_HOST value of foo.example.com:8000, request.domain will return foo.example. com. Note that this value cannot be set on the request. To set the host value use webob.request.Request. host() instead. classmethod from_bytes(b) Create a request from HTTP bytes data. If the bytes contain extra data after the request, raise a ValueError. classmethod from_file(fp) Read a request from a file-like object (it must implement .read(size) and .readline()). It will read up to the end of the request, not the end of the file (unless the request is a POST or PUT and has no Content-Length, in that case, the entire file is read). This reads the request as represented by str(req); it may not read every valid HTTP request properly. get_response(application=None, catch_exc_info=False) Like .call_application(application), except returns a response object with .status, . headers, and .body attributes. This will use self.ResponseClass to figure out the class of the response object to return. If application is not given, this will send the request to self.make_default_send_app() headers All the request headers as a case-insensitive dictionary-like object. host Host name provided in HTTP_HOST, with fall-back to SERVER_NAME host_port The effective server port number as a string. If the HTTP_HOST header exists in the WSGI environ, this attribute returns the port number present in that header. If the HTTP_HOST header exists but contains no explicit port number: if the WSGI url scheme is “https” , this attribute returns “443”, if the WSGI url scheme is “http”, this attribute returns “80” . If no HTTP_HOST header is present in the environ at all, this attribute will return the value of the SERVER_PORT header (which is guaranteed to be present). host_url The URL through the host (no path) http_version Gets and sets the SERVER_PROTOCOL key in the environment. if_match Gets and sets the If-Match header (HTTP spec section 14.24). Converts it as a Etag.
4.6. webob.request – Request 49 WebOb Documentation, Release 1.6.4
if_modified_since Gets and sets the If-Modified-Since header (HTTP spec section 14.25). Converts it using HTTP date. if_none_match Gets and sets the If-None-Match header (HTTP spec section 14.26). Converts it as a Etag. if_range Gets and sets the If-Range header (HTTP spec section 14.27). Converts it using IfRange object. if_unmodified_since Gets and sets the If-Unmodified-Since header (HTTP spec section 14.28). Converts it using HTTP date. is_body_readable webob.is_body_readable is a flag that tells us that we can read the input stream even though CON- TENT_LENGTH is missing. This allows FakeCGIBody to work and can be used by servers to support chunked encoding in requests. For background see https://bitbucket.org/ianb/webob/issue/6 is_body_seekable Gets and sets the webob.is_body_seekable key in the environment. is_xhr Is X-Requested-With header present and equal to XMLHttpRequest? Note: this isn’t set by every XMLHttpRequest request, it is only set if you are using a Javascript library that sets it (or you set the header yourself manually). Currently Prototype and jQuery are known to set this header. json Access the body of the request as JSON json_body Access the body of the request as JSON make_body_seekable() This forces environ['wsgi.input'] to be seekable. That means that, the content is copied into a BytesIO or temporary file and flagged as seekable, so that it will not be unnecessarily copied again. After calling this method the .body_file is always seeked to the start of file and .content_length is not None. The choice to copy to BytesIO is made from self.request_body_tempfile_limit make_tempfile() Create a tempfile to store big request body. This API is not stable yet. A ‘size’ argument might be added. max_forwards Gets and sets the Max-Forwards header (HTTP spec section 14.31). Converts it using int. method Gets and sets the REQUEST_METHOD key in the environment. params A dictionary-like object containing both the parameters from the query string and request body. path The path of the request, without host or query string path_info Gets and sets the PATH_INFO key in the environment.
50 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
path_info_peek() Returns the next segment on PATH_INFO, or None if there is no next segment. Doesn’t modify the environment. path_info_pop(pattern=None) ‘Pops’ off the next segment of PATH_INFO, pushing it onto SCRIPT_NAME, and returning the popped segment. Returns None if there is nothing left on PATH_INFO. Does not return '' when there’s an empty segment (like /path//path); these segments are just ignored. Optional pattern argument is a regexp to match the return value before returning. If there is no match, no changes are made to the request and None is returned. path_qs The path of the request, without host but with query string path_url The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING pragma Gets and sets the Pragma header (HTTP spec section 14.32). query_string Gets and sets the QUERY_STRING key in the environment. range Gets and sets the Range header (HTTP spec section 14.35). Converts it using Range object. referer Gets and sets the Referer header (HTTP spec section 14.36). referrer Gets and sets the Referer header (HTTP spec section 14.36). relative_url(other_url, to_application=False) Resolve other_url relative to the request URL. If to_application is True, then resolve it relative to the URL with only SCRIPT_NAME remote_addr Gets and sets the REMOTE_ADDR key in the environment. remote_user Gets and sets the REMOTE_USER key in the environment. remove_conditional_headers(remove_encoding=True, remove_range=True, re- move_match=True, remove_modified=True) Remove headers that make the request conditional. These headers can cause the response to be 304 Not Modified, which in some cases you may not want to be possible. This does not remove headers like If-Match, which are used for conflict detection. scheme Gets and sets the wsgi.url_scheme key in the environment. script_name Gets and sets the SCRIPT_NAME key in the environment. send(application=None, catch_exc_info=False) Like .call_application(application), except returns a response object with .status, . headers, and .body attributes. This will use self.ResponseClass to figure out the class of the response object to return.
4.6. webob.request – Request 51 WebOb Documentation, Release 1.6.4
If application is not given, this will send the request to self.make_default_send_app() server_name Gets and sets the SERVER_NAME key in the environment. server_port Gets and sets the SERVER_PORT key in the environment. Converts it using int. text Get/set the text value of the body upath_info Gets and sets the PATH_INFO key in the environment. url The full request URL, including QUERY_STRING url_encoding Gets and sets the webob.url_encoding key in the environment. urlargs Return any positional variables matched in the URL. Takes values from environ['wsgiorg.routing_args']. Systems like routes set this value. urlvars Return any named variables matched in the URL. Takes values from environ['wsgiorg.routing_args']. Systems like routes set this value. uscript_name Gets and sets the SCRIPT_NAME key in the environment. user_agent Gets and sets the User-Agent header (HTTP spec section 14.43). webob.response – Response
Response class webob.response.Response(body=None, status=None, headerlist=None, app_iter=None, con- tent_type=None, conditional_response=None, **kw) Represents a WSGI response accept_ranges Gets and sets the Accept-Ranges header (HTTP spec section 14.5). age Gets and sets the Age header (HTTP spec section 14.6). Converts it using int. allow Gets and sets the Allow header (HTTP spec section 14.7). Converts it using list. app_iter Returns the app_iter of the response. If body was set, this will create an app_iter from that body (a single-item list) app_iter_range(start, stop) Return a new app_iter built from the response app_iter, that serves up only the given start:stop range.
52 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
body The body of the response, as a str. This will read in the entire app_iter if necessary. body_file A file-like object that can be used to write to the body. If you passed in a list app_iter, that app_iter will be modified by writes. cache_control Get/set/modify the Cache-Control header (HTTP spec section 14.9) charset Get/set the charset (in the Content-Type) conditional_response_app(environ, start_response) Like the normal __call__ interface, but checks conditional headers: •If-Modified-Since (304 Not Modified; only on GET, HEAD) •If-None-Match (304 Not Modified; only on GET, HEAD) •Range (406 Partial Content; only on GET, HEAD) content_disposition Gets and sets the Content-Disposition header (HTTP spec section 19.5.1). content_encoding Gets and sets the Content-Encoding header (HTTP spec section 14.11). content_language Gets and sets the Content-Language header (HTTP spec section 14.12). Converts it using list. content_length Gets and sets the Content-Length header (HTTP spec section 14.17). Converts it using int. content_location Gets and sets the Content-Location header (HTTP spec section 14.14). content_md5 Gets and sets the Content-MD5 header (HTTP spec section 14.14). content_range Gets and sets the Content-Range header (HTTP spec section 14.16). Converts it using ContentRange object. content_type Get/set the Content-Type header (or None), without the charset or any parameters. If you include parameters (or ; at all) when setting the content_type, any existing parameters will be deleted; otherwise they will be preserved. content_type_params A dictionary of all the parameters in the content type. (This is not a view, set to change, modifications of the dict would not be applied otherwise) copy() Makes a copy of the response date Gets and sets the Date header (HTTP spec section 14.18). Converts it using HTTP date. delete_cookie(name, path=’/’, domain=None) Delete a cookie from the client. Note that path and domain must match how the cookie was originally set. This sets the cookie to the empty string, and max_age=0 so that it should expire immediately.
4.7. webob.response – Response 53 WebOb Documentation, Release 1.6.4
encode_content(encoding=’gzip’, lazy=False) Encode the content with the given encoding (only gzip and identity are supported). etag Gets and sets the ETag header (HTTP spec section 14.19). Converts it using Entity tag. expires Gets and sets the Expires header (HTTP spec section 14.21). Converts it using HTTP date. classmethod from_file(fp) Reads a response from a file-like object (it must implement .read(size) and .readline()). It will read up to the end of the response, not the end of the file. This reads the response as represented by str(resp); it may not read every valid HTTP response prop- erly. Responses must have a Content-Length headerlist The list of response headers headers The headers in a dictionary-like object json Access the body of the response as JSON json_body Access the body of the response as JSON last_modified Gets and sets the Last-Modified header (HTTP spec section 14.29). Converts it using HTTP date. location Gets and sets the Location header (HTTP spec section 14.30). md5_etag(body=None, set_content_md5=False) Generate an etag for the response object using an MD5 hash of the body (the body parameter, or self. body if not given) Sets self.etag If set_content_md5 is True sets self.content_md5 as well merge_cookies(resp) Merge the cookies that were set on this response with the given resp object (which can be any WSGI application). If the resp is a webob.Response object, then the other object will be modified in-place. pragma Gets and sets the Pragma header (HTTP spec section 14.32). retry_after Gets and sets the Retry-After header (HTTP spec section 14.37). Converts it using HTTP date or delta seconds. server Gets and sets the Server header (HTTP spec section 14.38). set_cookie(name=None, value=’‘, max_age=None, path=’/’, domain=None, secure=False, httponly=False, comment=None, expires=None, overwrite=False, key=None) Set (add) a cookie for the response. Arguments are: name
54 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
The cookie name. value The cookie value, which should be a string or None. If value is None, it’s equivalent to calling the webob.response.Response.unset_cookie() method for this cookie key (it effectively deletes the cookie on the client). max_age An integer representing a number of seconds, datetime.timedelta, or None. This value is used as the Max-Age of the generated cookie. If expires is not passed and this value is not None, the max_age value will also influence the Expires value of the cookie (Expires will be set to now + max_age). If this value is None, the cookie will not have a Max-Age value (unless expires is set). If both max_age and expires are set, this value takes precedence. path A string representing the cookie Path value. It defaults to /. domain A string representing the cookie Domain, or None. If domain is None, no Domain value will be sent in the cookie. secure A boolean. If it’s True, the secure flag will be sent in the cookie, if it’s False, the secure flag will not be sent in the cookie. httponly A boolean. If it’s True, the HttpOnly flag will be sent in the cookie, if it’s False, the HttpOnly flag will not be sent in the cookie. comment A string representing the cookie Comment value, or None. If comment is None, no Comment value will be sent in the cookie. expires A datetime.timedelta object representing an amount of time, datetime.datetime or None. A non-None value is used to generate the Expires value of the generated cookie. If max_age is not passed, but this value is not None, it will influence the Max-Age header. If this value is None, the Expires cookie value will be unset (unless max_age is set). If max_age is set, it will be used to generate the expires and this value is ignored. If a datetime.datetime is provided it has to either be timezone aware or be based on UTC. datetime.datetime objects that are local time are not supported. Timezone aware datetime.datetime objects are converted to UTC. This argument will be removed in future versions of WebOb (version 1.10). overwrite If this key is True, before setting the cookie, unset any existing cookie. status The status string status_code The status as an integer
4.7. webob.response – Response 55 WebOb Documentation, Release 1.6.4
status_int The status as an integer text Get/set the text value of the body (using the charset of the Content-Type) ubody Deprecated alias for .text unicode_body Deprecated alias for .text unset_cookie(name, strict=True) Unset a cookie with the given name (remove it from the response). vary Gets and sets the Vary header (HTTP spec section 14.44). Converts it using list. www_authenticate Gets and sets the WWW-Authenticate header (HTTP spec section 14.47). Converts it using parse_auth and serialize_auth. class webob.response.AppIterRange(app_iter, start, stop) Wraps an app_iter, returning just a range of bytes webob.static – Serving static files class webob.static.FileApp(filename, **kw) An application that will send the file at the given filename. Adds a mime type based on mimetypes.guess_type(). class webob.static.DirectoryApp(path, index_page=’index.html’, hide_index_with_redirect=False, **kw) An application that serves up the files in a given directory. This will serve index files (by default index.html), or set index_page=None to disable this. If you set hide_index_with_redirect=True (it defaults to False) then requests to, e.g., /index.html will be redirected to /. To customize FileApp instances creation (which is what actually serves the responses), override the make_fileapp method. webob – Request/Response objects
Headers
Accept-*
Parses a variety of Accept-* headers. These headers generally take the form of: value1; q=0.5, value2; q=0
Where the q parameter is optional. In theory other parameters exists, but this ignores them.
56 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4
class webob.acceptparse.Accept(header_value) Represents a generic Accept-* style header. This object should not be modified. To add items you can use accept_obj + 'accept_thing' to get a new object best_match(offers, default_match=None) Returns the best match in the sequence of offered types. The sequence can be a simple sequence, or you can have (match, server_quality) items in the sequence. If you have these tuples then the client quality is multiplied by the server_quality to get a total. If two matches have equal weight, then the one that shows up first in the offers list will be returned. But among matches with the same quality the match to a more specific requested type will be chosen. For example a match to text/* trumps /. default_match (default None) is returned if there is no intersection. static parse(value) Parse Accept-* style header. Return iterator of (value, quality) pairs. quality defaults to 1. quality(offer, modifier=1) Return the quality of the given offer. Returns None if there is no match (not 0). class webob.acceptparse.MIMEAccept(header_value) Represents the Accept header, which is a list of mimetypes. This class knows about mime wildcards, like image/* accept_html() Returns true if any HTML-like type is accepted accepts_html Returns true if any HTML-like type is accepted
Cache-Control
class webob.cachecontrol.CacheControl(properties, type) Represents the Cache-Control header. By giving a type of 'request' or 'response' you can control what attributes are allowed (some Cache- Control values only apply to requests or responses). copy() Returns a copy of this object. classmethod parse(header, updates_to=None, type=None) Parse the header, returning a CacheControl object. The object is bound to the request or response object updates_to, if that is given. update_dict alias of UpdateDict
Range and related headers class webob.byterange.Range(start, end) Represents the Range header.
4.9. webob – Request/Response objects 57 WebOb Documentation, Release 1.6.4
content_range(length) Works like range_for_length; returns None or a ContentRange object You can use it like:
response.content_range= req.range.content_range(response.content_length)
Though it’s still up to you to actually serve that content range! classmethod parse(header) Parse the header; may return None if header is invalid range_for_length(length) If there is only one range, and if it is satisfiable by the given length, then return a (start, end) non-inclusive range of bytes to serve. Otherwise return None class webob.byterange.ContentRange(start, stop, length) Represents the Content-Range header This header is start-stop/length, where start-stop and length can be * (represented as None in the attributes). classmethod parse(value) Parse the header. May return None if it cannot parse. class webob.etag.IfRange(etag)
classmethod parse(value) Parse this from a header value.
ETag class webob.etag.ETagMatcher(etags)
classmethod parse(value, strong=True) Parse this from a header value
Misc Functions and Internals webob.html_escape(s) HTML-escape a string or object This converts any non-string objects passed into it to strings (actually, using unicode()). All values returned are non-unicode strings (using num; entities for all non-ASCII characters). None is treated specially, and returns the empty string. class webob.headers.ResponseHeaders(*args, **kw) Dictionary view on the response headerlist. Keys are normalized for case and whitespace. class webob.headers.EnvironHeaders(environ) An object that represents the headers as present in a WSGI environment. This object is a wrapper (with no internal state) for a WSGI request object, representing the CGI-style HTTP_* keys as a dictionary. Because a CGI environment can only hold one value for each key, this dictionary is single-valued (unlike outgoing headers).
58 Chapter 4. API Documentation WebOb Documentation, Release 1.6.4 class webob.cachecontrol.UpdateDict Dict that has a callback on all updates
4.9. webob – Request/Response objects 59 WebOb Documentation, Release 1.6.4
60 Chapter 4. API Documentation CHAPTER 5
Request
The request object is a wrapper around the WSGI environ dictionary. This dictionary contains keys for each header, keys that describe the request (including the path and query string), a file-like object for the request body, and a variety of custom keys. You can always access the environ with req.environ. Some of the most important/interesting attributes of a request object: req.method: The request method, e.g., 'GET', 'POST' req.GET: A dictionary-like object with all the variables in the query string. req.POST: A dictionary-like object with all the variables in the request body. This only has variables if the request was a POST and it is a form submission. req.params: A dictionary-like object with a combination of everything in req.GET and req.POST. req.body: The contents of the body of the request. This contains the entire request body as a string. This is useful when the request is a POST that is not a form submission, or a request like a PUT. You can also get req.body_file for a file-like object. req.cookies: A simple dictionary of all the cookies. req.headers: A dictionary of all the headers. This is dictionary is case-insensitive. req.urlvars and req.urlargs: req.urlvars is the keyword parameters associated with the request URL. req.urlargs are the positional parameters. These are set by products like Routes and Selector. Also, for standard HTTP request headers there are usually attributes, for instance: req.accept_language, req. content_length, req.user_agent, as an example. These properties expose the parsed form of each header, for whatever parsing makes sense. For instance, req.if_modified_since returns a datetime object (or None if the header is was not provided). Details are in the Request reference.
URLs
In addition to these attributes, there are several ways to get the URL of the request. I’ll show various values for an example URL http://localhost/app-root/doc?article_id=10, where the application is mounted at http://localhost/app-root.
61 WebOb Documentation, Release 1.6.4 req.url: The full request URL, with query string, e.g., 'http://localhost/app-root/doc? article_id=10' req.application_url: The URL of the application (just the SCRIPT_NAME portion of the path, not PATH_INFO). E.g., 'http://localhost/app-root' req.host_url: The URL with the host, e.g., 'http://localhost' req.relative_url(url, to_application=False): Gives a URL, relative to the current URL. If to_application is True, then resolves it relative to req.application_url.
Methods
There are several methods in webob.Request but only a few you’ll use often: Request.blank(base_url): Creates a new request with blank information, based at the given URL. This can be useful for subrequests and artificial requests. You can also use req.copy() to copy an existing request, or for subrequests req.copy_get() which copies the request but always turns it into a GET (which is safer to share for subrequests). req.get_response(wsgi_application): This method calls the given WSGI application with this request, and returns a Response object. You can also use this for subrequests or testing.
Unicode
Many of the properties in the request object will return unicode values if the request encoding/charset is provided. The client can indicate the charset with something like Content-Type: application/ x-www-form-urlencoded; charset=utf8, but browsers seldom set this. You can set the charset with req. charset = 'utf8', or during instantiation with Request(environ, charset='utf8'). If you subclass Request you can also set charset as a class-level attribute. If it is set, then req.POST, req.GET, req.params, and req.cookies will contain unicode strings.
62 Chapter 5. Request CHAPTER 6
Response
The response object looks a lot like the request object, though with some differences. The request object wraps a single environ object; the response object has three fundamental parts (based on WSGI): response.status: The response code plus message, like '200 OK'. To set the code without the reason, use response.status_code = 200. response.headerlist: A list of all the headers, like [('Content-Type', 'text/html')]. There’s a case-insensitive dictionary-like object in response.headers that also allows you to access these same headers. response.app_iter: An iterable (such as a list or generator) that will produce the content of the response. This is also accessible as response.body (a string), response.unicode_body (a unicode object, in- formed by response.charset), and response.body_file (a file-like object; writing to it appends to app_iter). Everything else in the object derives from this underlying state. Here’s the highlights: response.content_type: The content type not including the charset parameter. Typical use: response. content_type = 'text/html'. You can subclass Response and add a class-level attribute default_content_type to set this automatically on instantiation. response.charset: The charset parameter of the content-type, it also informs encoding in response. unicode_body. response.content_type_params is a dictionary of all the parameters. response.request: This optional attribute can point to the request object associated with this response object. response.set_cookie(key, value, max_age=None, path='/', domain=None, secure=None, httponly=False, version=None, comment=None): Set a cookie. The keyword arguments control the various cookie parameters. The max_age argument is the length for the cookie to live in seconds (you may also use a timedelta object). The Expires‘ key will also be set based on the value of max_age. response.delete_cookie(key, path='/', domain=None): Delete a cookie from the client. This sets max_age to 0 and the cookie value to ''. response.cache_expires(seconds=0): This makes this response cachable for the given number of sec- onds, or if seconds is 0 then the response is uncacheable (this also sets the Expires header).
63 WebOb Documentation, Release 1.6.4 response(environ, start_response): The response object is a WSGI application. As an applica- tion, it acts according to how you create it. It can do conditional responses if you pass conditional_response=True when instantiating (or set that attribute later). It can also do HEAD and Range requests.
Headers
Like the request, most HTTP response headers are available as properties. These are parsed, so you can do things like response.last_modified = os.path.getmtime(filename). See also: webob.response.Response
Instantiating the Response
Of course most of the time you just want to make a response. Generally any attribute of the response can be passed in as a keyword argument to the class; e.g.: response= Response(body='hello world!', content_type='text/plain')
The status defaults to '200 OK'. The content_type does not default to anything, though if you subclass Response and set default_content_type you can override this behavior.
64 Chapter 6. Response CHAPTER 7
Exceptions
To facilitate error responses like 404 Not Found, the module webob.exc contains classes for each kind of error response. These include boring but appropriate error bodies. Each class is named webob.exc.HTTP*, where * is the reason for the error. For instance, webob.exc. HTTPNotFound. It subclasses Response, so you can manipulate the instances in the same way. A typical example is: response= HTTPNotFound('There is no such resource') # or: response= HTTPMovedPermanently(location=new_url)
You can use this like: try: ... stuff... raise HTTPNotFound('No such resource') except HTTPException, e: return e(environ, start_response)
The exceptions are still WSGI applications, but you cannot set attributes like content_type, charset, etc. on these exception objects.
65 WebOb Documentation, Release 1.6.4
66 Chapter 7. Exceptions CHAPTER 8
Multidict
Several parts of WebOb use a “multidict”; this is a dictionary where a key can have multiple values. The quintessential example is a query string like ?pref=red&pref=blue; the pref variable has two values: red and blue. In a multidict, when you do request.GET['pref'] you’ll get back only 'blue' (the last value of pref). Sometimes returning a string, and sometimes returning a list, is the cause of frequent exceptions. If you want all the values back, use request.GET.getall('pref'). If you want to be sure there is one and only one value, use request.GET.getone('pref'), which will raise an exception if there is zero or more than one value for pref. When you use operations like request.GET.items() you’ll get back something like [('pref', 'red'), ('pref', 'blue')]. All the key/value pairs will show up. Similarly request.GET.keys() returns ['pref', 'pref']. Multidict is a view on a list of tuples; all the keys are ordered, and all the values are or- dered.
67 WebOb Documentation, Release 1.6.4
68 Chapter 8. Multidict CHAPTER 9
Example
The file-serving example shows how to do more advanced HTTP techniques, while the comment middleware example shows middleware. For applications it’s more reasonable to use WebOb in the context of a larger framework. Pylons uses WebOb in 0.9.7+.
WebOb File-Serving Example
This document shows how you can make a static-file-serving application using WebOb. We’ll quickly build this up from minimal functionality to a high-quality file serving application.
Note: Starting from 1.2b4, WebOb ships with a webob.static module which implements a webob.static. FileApp WSGI application similar to the one described below. This document stays as a didactic example how to serve files with WebOb, but you should consider using applications from webob.static in production.
First we’ll setup a really simple shim around our application, which we can use as we improve our application:
>>> from webob import Request, Response >>> import os >>> class FileApp(object): ... def __init__(self, filename): ... self.filename= filename ... def __call__(self, environ, start_response): ... res= make_response(self.filename) ... return res(environ, start_response) >>> import mimetypes >>> def get_mimetype(filename): ... type, encoding= mimetypes.guess_type(filename) ... # We'll ignore encoding, even though we shouldn't really ... return type or 'application/octet-stream'
69 WebOb Documentation, Release 1.6.4
Now we can make different definitions of make_response. The simplest version:
>>> def make_response(filename): ... res= Response(content_type=get_mimetype(filename)) ... res.body= open(filename,'rb').read() ... return res
We’ll test it out with a file test-file.txt in the WebOb doc directory, which has the following content:
This is a test. Hello test people!
Let’s give it a shot:
>>> fn= os.path.join(doc_dir,'file-example-code/test-file.txt') >>> open(fn).read() 'This is a test. Hello test people!' >>> app= FileApp(fn) >>> req= Request.blank('/') >>> print req.get_response(app) 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 35
This is a test. Hello test people!
Well, that worked. But it’s not a very fancy object. First, it reads everything into memory, and that’s bad. We’ll create an iterator instead:
>>> class FileIterable(object): ... def __init__(self, filename): ... self.filename= filename ... def __iter__(self): ... return FileIterator(self.filename) >>> class FileIterator(object): ... chunk_size= 4096 ... def __init__(self, filename): ... self.filename= filename ... self.fileobj= open(self.filename,'rb') ... def __iter__(self): ... return self ... def next(self): ... chunk= self.fileobj.read(self.chunk_size) ... if not chunk: ... raise StopIteration ... return chunk ... __next__= next # py3 compat >>> def make_response(filename): ... res= Response(content_type=get_mimetype(filename)) ... res.app_iter= FileIterable(filename) ... res.content_length= os.path.getsize(filename) ... return res
And testing:
>>> req= Request.blank('/') >>> print req.get_response(app) 200 OK Content-Type: text/plain; charset=UTF-8
70 Chapter 9. Example WebOb Documentation, Release 1.6.4
Content-Length: 35
This is a test. Hello test people!
Well, that doesn’t look different, but lets imagine that it’s different because we know we changed some code. Now to add some basic metadata to the response:
>>> def make_response(filename): ... res= Response(content_type=get_mimetype(filename), ... conditional_response=True) ... res.app_iter= FileIterable(filename) ... res.content_length= os.path.getsize(filename) ... res.last_modified= os.path.getmtime(filename) ... res.etag=' %s-%s-%s'% (os.path.getmtime(filename), ... os.path.getsize(filename), hash(filename)) ... return res
Now, with conditional_response on, and with last_modified and etag set, we can do conditional re- quests:
>>> req= Request.blank('/') >>> res= req.get_response(app) >>> print res 200 OK Content-Type: text/plain; charset=UTF-8 Content-Length: 35 Last-Modified: ... GMT ETag: ...-...
This is a test. Hello test people! >>> req2= Request.blank('/') >>> req2.if_none_match= res.etag >>> req2.get_response(app)
We can even do Range requests, but it will currently involve iterating through the file unnecessarily. When there’s a range request (and you set conditional_response=True) the application will satisfy that request. But with an arbitrary iterator the only way to do that is to run through the beginning of the iterator until you get to the chunk that the client asked for. We can do better because we can use fileobj.seek(pos) to move around the file much more efficiently. So we’ll add an extra method, app_iter_range, that Response looks for:
>>> class FileIterable(object): ... def __init__(self, filename, start=None, stop=None): ... self.filename= filename ... self.start= start ... self.stop= stop ... def __iter__(self): ... return FileIterator(self.filename, self.start, self.stop) ... def app_iter_range(self, start, stop): ... return self.__class__(self.filename, start, stop) >>> class FileIterator(object): ... chunk_size= 4096
9.1. WebOb File-Serving Example 71 WebOb Documentation, Release 1.6.4
... def __init__(self, filename, start, stop): ... self.filename= filename ... self.fileobj= open(self.filename,'rb') ... if start: ... self.fileobj.seek(start) ... if stop is not None: ... self.length= stop- start ... else: ... self.length= None ... def __iter__(self): ... return self ... def next(self): ... if self.length is not None and self.length<=0: ... raise StopIteration ... chunk= self.fileobj.read(self.chunk_size) ... if not chunk: ... raise StopIteration ... if self.length is not None: ... self.length-= len(chunk) ... if self.length<0: ... # Chop off the extra: ... chunk= chunk[:self.length] ... return chunk ... __next__= next # py3 compat
Now we’ll test it out:
>>> req= Request.blank('/') >>> res= req.get_response(app) >>> req2= Request.blank('/') >>> # Re-fetch the first 5 bytes: >>> req2.range=(0,5) >>> res2= req2.get_response(app) >>> res2
Wiki Example
author Ian Bicking
72 Chapter 9. Example WebOb Documentation, Release 1.6.4
Contents
• Wiki Example – Introduction – Code – Creating an Application – The WSGI Application – The Domain Object – URLs, PATH_INFO, and SCRIPT_NAME – Back to the Application – The Edit Screen – Processing the Form – Cookies – Conclusion
Introduction
This is an example of how to write a WSGI application using WebOb. WebOb isn’t itself intended to write applications – it is not a web framework on its own – but it is possible to write applications using just WebOb. The file serving example is a better example of advanced HTTP usage. The comment middleware example is a better example of using middleware. This example provides some completeness by showing an application-focused end point. This example implements a very simple wiki.
Code
The finished code for this is available in docs/wiki-example-code/example.py – you can run that file as a script to try it out.
Creating an Application
A common pattern for creating small WSGI applications is to have a class which is instantiated with the configuration. For our application we’ll be storing the pages under a directory.
class WikiApp(object):
def __init__(self, storage_dir): self.storage_dir= os.path.abspath(os.path.normpath(storage_dir))
WSGI applications are callables like wsgi_app(environ, start_response). Instances of WikiApp are WSGI applications, so we’ll implement a __call__ method:
class WikiApp(object): ...
9.2. Wiki Example 73 WebOb Documentation, Release 1.6.4
def __call__(self, environ, start_response): # what we'll fill in
To make the script runnable we’ll create a simple command-line interface: if __name__ =='__main__': import optparse parser= optparse.OptionParser( usage='%prog --port=PORT' ) parser.add_option( '-p','--port', default='8080', dest='port', type='int', help='Port to serve on (default 8080)') parser.add_option( '--wiki-data', default='./wiki', dest='wiki_data', help='Place to put wiki data into (default ./wiki/)') options, args= parser.parse_args() print 'Writing wiki pages to %s'% options.wiki_data app= WikiApp(options.wiki_data) from wsgiref.simple_server import make_server httpd= make_server('localhost', options.port, app) print 'Serving on http://localhost:%s'% options.port try: httpd.serve_forever() except KeyboardInterrupt: print '^C'
There’s not much to talk about in this code block. The application is instantiated and served with the built-in module wsgiref.simple_server.
The WSGI Application
Of course all the interesting stuff is in that __call__ method. WebOb lets you ignore some of the details of WSGI, like what start_response really is. environ is a CGI-like dictionary, but webob.Request gives an object interface to it. webob.Response represents a response, and is itself a WSGI application. Here’s kind of the hello world of WSGI applications using these objects: from webob import Request, Response class WikiApp(object): ...
def __call__(self, environ, start_response): req= Request(environ) resp= Response( 'Hello %s!'% req.params.get('name','World')) return resp(environ, start_response) req.params.get('name', 'World') gets any query string parameter (like ?name=Bob), or if it’s a POST form request it will look for a form parameter name. We instantiate the response with the body of the response. You
74 Chapter 9. Example WebOb Documentation, Release 1.6.4 could also give keyword arguments like content_type='text/plain' (text/html is the default content type and 200 OK is the default status). For the wiki application we’ll support a couple different kinds of screens, and we’ll make our __call__ method dispatch to different methods depending on the request. We’ll support an action parameter like ?action=edit, and also dispatch on the method (GET, POST, etc, in req.method). We’ll pass in the request and expect a response object back. Also, WebOb has a series of exceptions in webob.exc, like webob.exc.HTTPNotFound, webob.exc. HTTPTemporaryRedirect, etc. We’ll also let the method raise one of these exceptions and turn it into a response. One last thing we’ll do in our __call__ method is create our Page object, which represents a wiki page. All this together makes: from webob import Request, Response from webob import exc class WikiApp(object): ...
def __call__(self, environ, start_response): req= Request(environ) action= req.params.get('action','view') # Here's where we get the Page domain object: page= self.get_page(req.path_info) try: try: # The method name is action_{action_param}_{request_method}: meth= getattr(self,'action_ %s_%s'% (action, req.method)) except AttributeError: # If the method wasn't found there must be # something wrong with the request: raise exc.HTTPBadRequest('No such action %r'% action) resp= meth(req, page) except exc.HTTPException, e: # The exception object itself is a WSGI application/response: resp=e return resp(environ, start_response)
The Domain Object
The Page domain object isn’t really related to the web, but it is important to implementing this. Each Page is just a file on the filesystem. Our get_page method figures out the filename given the path (the path is in req. path_info, which is all the path after the base path). The Page class handles getting and setting the title and content. Here’s the method to figure out the filename: import os class WikiApp(object): ...
def get_page(self, path): path= path.lstrip('/') if not path: # The path was '/', the home page
9.2. Wiki Example 75 WebOb Documentation, Release 1.6.4
path='index' path= os.path.join(self.storage_dir) path= os.path.normpath(path) if path.endswith('/'): path+='index' if not path.startswith(self.storage_dir): raise exc.HTTPBadRequest("Bad path") path+='.html' return Page(path)
Mostly this is just the kind of careful path construction you have to do when mapping a URL to a filename. While the server may normalize the path (so that a path like /../../ can’t be requested), you can never really be sure. By using os.path.normpath we eliminate these, and then we make absolutely sure that the resulting path is un- der our self.storage_dir with if not path.startswith(self.storage_dir): raise exc. HTTPBadRequest("Bad path"). Here’s the actual domain object: class Page(object): def __init__(self, filename): self.filename= filename
@property def exists(self): return os.path.exists(self.filename)
@property def title(self): if not self.exists: # we need to guess the title basename= os.path.splitext(os.path.basename(self.filename))[0] basename= re.sub(r'[_-]','', basename) return basename.capitalize() content= self.full_content match= re.search(r'
@property def full_content(self): f= open(self.filename,'rb') try: return f.read() finally: f.close()
@property def content(self): if not self.exists: return '' content= self.full_content match= re.search(r'
] *>(.*?)', content, re.I|re.S) return match.group(1)@property def mtime(self): if not self.exists: return None else:
76 Chapter 9. Example WebOb Documentation, Release 1.6.4
return int(os.stat(self.filename).st_mtime)
def set(self, title, content): dir= os.path.dirname(self.filename) if not os.path.exists(dir): os.makedirs(dir) new_content= """
˓→"%( title, content) f= open(self.filename,'wb') f.write(new_content) f.close()
Basically it provides a .title attribute, a .content attribute, the .mtime (last modified time), and the page can exist or not (giving appropriate guesses for title and content when the page does not exist). It encodes these on the filesystem as a simple HTML page that is parsed by some regular expressions. None of this really applies much to the web or WebOb, so I’ll leave it to you to figure out the details of this.
URLs, PATH_INFO, and SCRIPT_NAME
This is an aside for the tutorial, but an important concept. In WSGI, and accordingly with WebOb, the URL is split up into several pieces. Some of these are obvious and some not. An example: http://example.com:8080/wiki/article/12?version=10
There are several components here: • req.scheme: http • req.host: example.com:8080 • req.server_name: example.com • req.server_port: 8080 • req.script_name: /wiki • req.path_info: /article/12 • req.query_string: version=10 One non-obvious part is req.script_name and req.path_info. These correspond to the CGI environmental variables SCRIPT_NAME and PATH_INFO. req.script_name points to the application. You might have several applications in your site at different paths: one at /wiki, one at /blog, one at /. Each application doesn’t necessarily know about the others, but it has to construct its URLs properly – so any internal links to the wiki application should start with /wiki. Just as there are pieces to the URL, there are several properties in WebOb to construct URLs based on these: • req.host_url: http://example.com:8080 • req.application_url: http://example.com:8080/wiki • req.path_url: http://example.com:8080/wiki/article/12 • req.path: /wiki/article/12 • req.path_qs: /wiki/article/12?version=10
9.2. Wiki Example 77 WebOb Documentation, Release 1.6.4
• req.url: http://example.com:8080/wiki/article/12?version10 You can also create URLs with req.relative_url('some/other/page'). In this example that would re- solve to http://example.com:8080/wiki/article/some/other/page. You can also create a relative URL to the application URL (SCRIPT_NAME) like req.relative_url('some/other/page', True) which would be http://example.com:8080/wiki/some/other/page.
Back to the Application
We have a dispatching function with __call__ and we have a domain object with Page, but we aren’t actually doing anything. The dispatching goes to action_ACTION_METHOD, where ACTION defaults to view. So a simple page view will be action_view_GET. Let’s implement that:
class WikiApp(object): ...
def action_view_GET(self, req, page): if not page.exists: return exc.HTTPTemporaryRedirect( location=req.url+'?action=edit') text= self.view_template.substitute( page=page, req=req) resp= Response(text) resp.last_modified= page.mtime resp.conditional_response= True return resp
The first thing we do is redirect the user to the edit screen if the page doesn’t exist. exc. HTTPTemporaryRedirect is a response that gives a 307 Temporary Redirect response with the given location. Otherwise we fill in a template. The template language we’re going to use in this example is Tempita, a very simple template language with a similar interface to string.Template. The template actually looks like this:
from tempita import HTMLTemplate
VIEW_TEMPLATE= HTMLTemplate(""" \
{{page.title}}
Edit """)
class WikiApp(object):
78 Chapter 9. Example WebOb Documentation, Release 1.6.4
view_template= VIEW_TEMPLATE ...
As you can see it’s a simple template using the title and the body, and a link to the edit screen. We copy the template object into a class method (view_template = VIEW_TEMPLATE) so that potentially a subclass could override these templates. tempita.HTMLTemplate is a template that does automatic HTML escaping. Our wiki will just be written in plain HTML, so we disable escaping of the content with {{page.content|html}}. So let’s look at the action_view_GET method again:
def action_view_GET(self, req, page): if not page.exists: return exc.HTTPTemporaryRedirect( location=req.url+'?action=edit') text= self.view_template.substitute( page=page, req=req) resp= Response(text) resp.last_modified= page.mtime resp.conditional_response= True return resp
The template should be pretty obvious now. We create a response with Response(text), which already has a default Content-Type of text/html. To allow conditional responses we set resp.last_modified. You can set this attribute to a date, None (effec- tively removing the header), a time tuple (like produced by time.localtime()), or as in this case to an integer timestamp. If you get the value back it will always be a datetime object (or None). With this header we can process requests with If-Modified-Since headers, and return 304 Not Modified if appropriate. It won’t actually do that unless you set resp.conditional_response to True.
Note: If you subclass webob.Response you can set the class attribute default_conditional_response = True and this setting will be on by default. You can also set other defaults, like the default_charset ("utf8"), or default_content_type ("text/html").
The Edit Screen
The edit screen will be implemented in the method action_edit_GET. There’s a template and a very simple method:
EDIT_TEMPLATE= HTMLTemplate(""" \
Edit: {{page.title}}
{{else}}Create: {{page.title}}
{{endif}} """)class WikiApp(object): ...
edit_template= EDIT_TEMPLATE
def action_edit_GET(self, req, page): text= self.edit_template.substitute( page=page, req=req) return Response(text)
As you can see, all the action here is in the template. In